@steno-ai/engine 0.1.1 → 0.1.3
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/adapters/storage.d.ts +1 -0
- package/dist/adapters/storage.d.ts.map +1 -1
- package/dist/extraction/llm-extractor.d.ts.map +1 -1
- package/dist/extraction/llm-extractor.js +5 -3
- package/dist/extraction/llm-extractor.js.map +1 -1
- package/dist/extraction/pipeline.d.ts.map +1 -1
- package/dist/extraction/pipeline.js +5 -1
- package/dist/extraction/pipeline.js.map +1 -1
- package/dist/extraction/prompts.d.ts +2 -2
- package/dist/extraction/prompts.d.ts.map +1 -1
- package/dist/extraction/prompts.js +12 -3
- package/dist/extraction/prompts.js.map +1 -1
- package/package.json +6 -2
- package/src/adapters/cache.js +2 -0
- package/src/adapters/embedding.js +2 -0
- package/src/adapters/llm.js +2 -0
- package/src/adapters/perplexity-embedding.js +78 -0
- package/src/adapters/storage.js +2 -0
- package/src/adapters/storage.ts +1 -0
- package/src/config.d.ts +211 -1
- package/src/config.d.ts.map +1 -1
- package/src/config.js +92 -0
- package/src/config.js.map +1 -1
- package/src/extraction/contradiction.js +23 -0
- package/src/extraction/dedup.js +93 -0
- package/src/extraction/dedup.js.map +1 -1
- package/src/extraction/entity-extractor.d.ts.map +1 -1
- package/src/extraction/entity-extractor.js +145 -0
- package/src/extraction/entity-extractor.js.map +1 -1
- package/src/extraction/hasher.js +8 -0
- package/src/extraction/heuristic.js +282 -0
- package/src/extraction/llm-extractor.d.ts +3 -1
- package/src/extraction/llm-extractor.d.ts.map +1 -1
- package/src/extraction/llm-extractor.js +238 -0
- package/src/extraction/llm-extractor.js.map +1 -1
- package/src/extraction/llm-extractor.ts +7 -5
- package/src/extraction/pipeline.d.ts +3 -0
- package/src/extraction/pipeline.d.ts.map +1 -1
- package/src/extraction/pipeline.js +398 -0
- package/src/extraction/pipeline.js.map +1 -1
- package/src/extraction/pipeline.ts +6 -1
- package/src/extraction/prompts.d.ts +28 -0
- package/src/extraction/prompts.d.ts.map +1 -0
- package/src/extraction/prompts.js +196 -0
- package/src/extraction/prompts.js.map +1 -1
- package/src/extraction/prompts.ts +12 -3
- package/src/extraction/sliding-window.js +84 -0
- package/src/extraction/sliding-window.js.map +1 -1
- package/src/extraction/types.d.ts +12 -0
- package/src/extraction/types.d.ts.map +1 -1
- package/src/extraction/types.js +2 -0
- package/src/feedback/tracker.js +90 -0
- package/src/models/api-key.d.ts +2 -2
- package/src/models/api-key.js +21 -0
- package/src/models/edge.d.ts +6 -6
- package/src/models/edge.js +29 -0
- package/src/models/entity.d.ts +2 -2
- package/src/models/entity.js +22 -0
- package/src/models/extraction.d.ts +6 -6
- package/src/models/extraction.js +40 -0
- package/src/models/fact-entity.js +14 -0
- package/src/models/fact.d.ts +191 -0
- package/src/models/fact.d.ts.map +1 -0
- package/src/models/fact.js +72 -0
- package/src/models/fact.js.map +1 -0
- package/src/models/index.js +13 -0
- package/src/models/memory-access.d.ts +4 -4
- package/src/models/memory-access.js +33 -0
- package/src/models/session.js +23 -0
- package/src/models/tenant.d.ts +248 -14
- package/src/models/tenant.d.ts.map +1 -1
- package/src/models/tenant.js +23 -0
- package/src/models/trigger.d.ts +5 -5
- package/src/models/trigger.js +41 -0
- package/src/models/usage-record.js +14 -0
- package/src/models/webhook.d.ts +1 -1
- package/src/models/webhook.js +25 -0
- package/src/retrieval/compound-search.d.ts.map +1 -1
- package/src/retrieval/compound-search.js +87 -0
- package/src/retrieval/compound-search.js.map +1 -1
- package/src/retrieval/contradiction-surfacer.js +64 -0
- package/src/retrieval/embedding-cache.js +56 -0
- package/src/retrieval/fusion.d.ts +1 -0
- package/src/retrieval/fusion.d.ts.map +1 -1
- package/src/retrieval/fusion.js +87 -0
- package/src/retrieval/fusion.js.map +1 -1
- package/src/retrieval/graph-traversal.d.ts +2 -1
- package/src/retrieval/graph-traversal.d.ts.map +1 -1
- package/src/retrieval/graph-traversal.js +208 -0
- package/src/retrieval/graph-traversal.js.map +1 -1
- package/src/retrieval/query-expansion.js +76 -0
- package/src/retrieval/reranker.js +47 -0
- package/src/retrieval/salience-scorer.js +41 -0
- package/src/retrieval/search.d.ts.map +1 -1
- package/src/retrieval/search.js +228 -0
- package/src/retrieval/search.js.map +1 -1
- package/src/retrieval/temporal-scorer.d.ts +18 -0
- package/src/retrieval/temporal-scorer.d.ts.map +1 -0
- package/src/retrieval/temporal-scorer.js +106 -0
- package/src/retrieval/temporal-scorer.js.map +1 -0
- package/src/retrieval/trigger-matcher.d.ts.map +1 -1
- package/src/retrieval/trigger-matcher.js +134 -0
- package/src/retrieval/trigger-matcher.js.map +1 -1
- package/src/retrieval/types.d.ts +4 -0
- package/src/retrieval/types.d.ts.map +1 -1
- package/src/retrieval/types.js +9 -0
- package/src/retrieval/types.js.map +1 -1
- package/src/retrieval/vector-search.d.ts.map +1 -1
- package/src/retrieval/vector-search.js +24 -0
- package/src/retrieval/vector-search.js.map +1 -1
- package/src/salience/decay.js +15 -0
- package/src/scratchpad/scratchpad.js +107 -0
- package/src/sessions/manager.d.ts +11 -0
- package/src/sessions/manager.d.ts.map +1 -0
- package/src/sessions/manager.js +63 -0
- package/src/sessions/manager.js.map +1 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// PASS 1: FACT EXTRACTION — Simple, focused, one job
|
|
3
|
+
// =============================================================================
|
|
4
|
+
export const FACT_EXTRACTION_PROMPT = `You are a memory extraction engine. Extract facts from text for a personal AI memory system.
|
|
5
|
+
|
|
6
|
+
## WHO IS "USER"?
|
|
7
|
+
|
|
8
|
+
Messages labeled "user" are from THE PERSON whose memories we are storing.
|
|
9
|
+
Messages labeled "assistant" or any other role are from OTHER people.
|
|
10
|
+
|
|
11
|
+
CRITICAL: Focus on facts FROM "user" messages, but ALSO extract notable facts about other people mentioned in the conversation. If the assistant/conversation partner shares personal information (e.g., "I painted a sunrise last year", "I realized self-care is important"), store it as "User's conversation partner [Name] painted a sunrise in [year]".
|
|
12
|
+
|
|
13
|
+
For identity/trait facts, state them DIRECTLY:
|
|
14
|
+
- "User is a transgender woman" (not just "User went to a transgender conference")
|
|
15
|
+
- "User works at Brightwell Capital" (not just "User had a busy day at work")
|
|
16
|
+
- "User is researching adoption agencies" (not just "User attended a meeting about adoption")
|
|
17
|
+
|
|
18
|
+
## RULES
|
|
19
|
+
|
|
20
|
+
1. Extract SELF-CONTAINED atomic facts. Each fact must be understandable on its own, without the original conversation.
|
|
21
|
+
|
|
22
|
+
2. **DATES ARE CRITICAL** — Resolve ALL temporal references to exact dates:
|
|
23
|
+
- "yesterday" → "on 7 May 2023"
|
|
24
|
+
- "last week" → "around 1 May 2023"
|
|
25
|
+
- "recently" → "in early May 2023"
|
|
26
|
+
- Look for date context like "[This conversation took place on 8 May, 2023]" and resolve ALL relative dates from it.
|
|
27
|
+
- EVERY event/activity fact MUST include WHEN it happened if the date can be inferred.
|
|
28
|
+
- BAD: "User went to an LGBTQ support group"
|
|
29
|
+
- GOOD: "User went to an LGBTQ support group on 7 May 2023"
|
|
30
|
+
|
|
31
|
+
3. Resolve ALL other references:
|
|
32
|
+
- Pronouns → names: "she said" → "Casey said"
|
|
33
|
+
- Places → full names: "there" → "at Brightwell Capital"
|
|
34
|
+
|
|
35
|
+
4. Be SPECIFIC, not vague:
|
|
36
|
+
BAD: "User had issues at work"
|
|
37
|
+
GOOD: "User's team at Brightwell Capital rambles too much in meetings"
|
|
38
|
+
|
|
39
|
+
5. Extract ALL facts, even minor ones. You cannot predict what will be asked later.
|
|
40
|
+
|
|
41
|
+
6. Write all facts in third person using "User" (e.g., "User prefers dark mode").
|
|
42
|
+
|
|
43
|
+
7. For conversation partners: ALSO extract their facts with their name.
|
|
44
|
+
- If Melanie says "I painted a sunrise last year" → "Melanie painted a sunrise in 2022"
|
|
45
|
+
- If Melanie says "I ran a charity race" → "Melanie ran a charity race"
|
|
46
|
+
|
|
47
|
+
8. For EVERY fact, include "ed" (event date) if the fact describes something that happened at a specific time.
|
|
48
|
+
- "User went to the gym on May 7" → ed: "2023-05-07"
|
|
49
|
+
- "User prefers dark mode" → ed: null (timeless preference)
|
|
50
|
+
If the conversation header says "[This conversation took place on 8 May, 2023]", set "dd" to "2023-05-08" for all facts.
|
|
51
|
+
|
|
52
|
+
## OUTPUT
|
|
53
|
+
|
|
54
|
+
Return ONLY a JSON object:
|
|
55
|
+
{"facts": [{"t": "fact text here", "i": 0.7, "ed": "2023-05-07", "dd": "2023-05-08"}, {"t": "another fact", "i": 0.3, "ed": null, "dd": "2023-05-08"}]}
|
|
56
|
+
|
|
57
|
+
- ed (eventDate): ISO date string of WHEN the event occurred, or null if not temporal
|
|
58
|
+
- dd (documentDate): ISO date string of when the conversation took place, from context header
|
|
59
|
+
|
|
60
|
+
Score importance (i) from 0.0 to 1.0:
|
|
61
|
+
- 0.9-1.0: Identity, health conditions, allergies, life events (birth, marriage, death)
|
|
62
|
+
- 0.7-0.8: Relationships, employment, education, strong preferences, plans
|
|
63
|
+
- 0.4-0.6: Activities, opinions, moderate preferences, daily events
|
|
64
|
+
- 0.1-0.3: Casual mentions, weather, trivial observations
|
|
65
|
+
|
|
66
|
+
Nothing else. No explanation, no markdown.`;
|
|
67
|
+
// =============================================================================
|
|
68
|
+
// PASS 2: GRAPH EXTRACTION — Entities + relationships from extracted facts
|
|
69
|
+
// =============================================================================
|
|
70
|
+
export const GRAPH_EXTRACTION_PROMPT = `You are a knowledge graph builder. Given a list of facts about a person, extract entities and relationships.
|
|
71
|
+
|
|
72
|
+
## ENTITIES
|
|
73
|
+
|
|
74
|
+
Extract only IMPORTANT named entities — proper nouns and specific things worth remembering. Aim for 3-8 entities total, not 40.
|
|
75
|
+
|
|
76
|
+
DO extract: People (Casey, Jamie), Organizations (Brightwell Capital), Places (Harbor Point), Products/Projects (LifePath, AirPods Max), Named activities (Catan, D&D)
|
|
77
|
+
DO NOT extract: Generic nouns (team, boss, meeting, work, food, gym), abstract concepts (motivation, stress), common objects (pizza, chair, phone)
|
|
78
|
+
|
|
79
|
+
## RELATIONSHIPS
|
|
80
|
+
|
|
81
|
+
Extract relationships between entities. Be SMART — infer from context:
|
|
82
|
+
- "User works at Google" → user works_at google
|
|
83
|
+
- "User loves Casey, plans to propose" → user partner_of casey
|
|
84
|
+
- "User's friend Jamie came over" → user friend_of jamie
|
|
85
|
+
|
|
86
|
+
Use snake_case relation names: works_at, partner_of, friend_of, lives_in, uses, studies, prefers, etc.
|
|
87
|
+
|
|
88
|
+
## OUTPUT
|
|
89
|
+
|
|
90
|
+
Return ONLY a JSON object:
|
|
91
|
+
{
|
|
92
|
+
"entities": [
|
|
93
|
+
{"name": "Casey", "entity_type": "person"},
|
|
94
|
+
{"name": "Brightwell Capital", "entity_type": "organization"}
|
|
95
|
+
],
|
|
96
|
+
"edges": [
|
|
97
|
+
{"source": "user", "target": "casey", "relation": "partner_of"},
|
|
98
|
+
{"source": "user", "target": "brightwell capital", "relation": "works_at"}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
entity_type must be one of: {ENTITY_TYPES}.
|
|
103
|
+
Entity names must be clean: no punctuation, no articles, no sentence fragments.
|
|
104
|
+
Return ONLY valid JSON.`;
|
|
105
|
+
export const DEFAULT_ENTITY_TYPES = ['person', 'organization', 'location', 'technology', 'concept', 'event'];
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// DEDUP PROMPT — Classify new facts against existing ones
|
|
108
|
+
// =============================================================================
|
|
109
|
+
export const DEDUP_PROMPT = `You are a memory deduplication engine. Given NEW facts and EXISTING facts in a memory store, classify each new fact.
|
|
110
|
+
|
|
111
|
+
For each new fact, decide:
|
|
112
|
+
- ADD: entirely new information, not covered by existing facts
|
|
113
|
+
- UPDATE: replaces or refines an existing fact (provide the index). The new fact SUPERSEDES the old one.
|
|
114
|
+
- EXTEND: adds new detail to an existing fact WITHOUT contradicting it (provide the index)
|
|
115
|
+
- NOOP: already covered by an existing fact — skip it
|
|
116
|
+
- CONTRADICT: conflicts with an existing fact (provide the index)
|
|
117
|
+
|
|
118
|
+
Return a JSON array:
|
|
119
|
+
[
|
|
120
|
+
{"fact": "...", "operation": "ADD"},
|
|
121
|
+
{"fact": "...", "operation": "UPDATE", "existing_index": 3},
|
|
122
|
+
{"fact": "...", "operation": "EXTEND", "existing_index": 5},
|
|
123
|
+
{"fact": "...", "operation": "NOOP"},
|
|
124
|
+
{"fact": "...", "operation": "CONTRADICT", "existing_index": 7}
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
Be conservative: prefer NOOP over ADD if the information is substantially similar.
|
|
128
|
+
Prefer EXTEND over UPDATE when the new fact adds detail without changing the core meaning.`;
|
|
129
|
+
/**
|
|
130
|
+
* Build the fact extraction prompt (Pass 1).
|
|
131
|
+
* Simple: extract facts as strings.
|
|
132
|
+
*/
|
|
133
|
+
export function buildFactExtractionPrompt(input) {
|
|
134
|
+
return [
|
|
135
|
+
{ role: 'system', content: FACT_EXTRACTION_PROMPT },
|
|
136
|
+
{ role: 'user', content: `Extract facts from this text:\n\n${input}` },
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build the graph extraction prompt (Pass 2).
|
|
141
|
+
* Takes extracted facts and produces entities + edges.
|
|
142
|
+
*/
|
|
143
|
+
export function buildGraphExtractionPrompt(facts, entityTypes, domainEntityTypes) {
|
|
144
|
+
const factsList = facts.map((f, i) => `${i + 1}. ${f}`).join('\n');
|
|
145
|
+
// Build entity type list — merge default names with domain-specific types
|
|
146
|
+
const defaultTypes = entityTypes ?? DEFAULT_ENTITY_TYPES;
|
|
147
|
+
const domainTypeNames = domainEntityTypes?.map(t => t.name.toLowerCase()) ?? [];
|
|
148
|
+
const allTypeNames = [...new Set([...defaultTypes, ...domainTypeNames])];
|
|
149
|
+
let prompt = GRAPH_EXTRACTION_PROMPT.replace('{ENTITY_TYPES}', allTypeNames.join(', '));
|
|
150
|
+
// If domain entity types have field definitions, add them to the prompt
|
|
151
|
+
if (domainEntityTypes && domainEntityTypes.length > 0) {
|
|
152
|
+
const typeDefinitions = domainEntityTypes.map(t => {
|
|
153
|
+
const fieldsStr = t.fields.length > 0
|
|
154
|
+
? '\n Fields: ' + t.fields.map(f => `${f.name} (${f.type}${f.required ? ', required' : ''}: ${f.description})`).join(', ')
|
|
155
|
+
: '';
|
|
156
|
+
return `- ${t.name}: "${t.description}"${fieldsStr}`;
|
|
157
|
+
}).join('\n');
|
|
158
|
+
prompt += `\n\nCustom entity type definitions:\n${typeDefinitions}\n\nWhen you identify an entity matching a custom type, include its fields in a "properties" object:\n{"name": "Acme Corp", "entity_type": "lead", "properties": {"company_size": "enterprise", "budget_range": "$50k-100k"}}`;
|
|
159
|
+
}
|
|
160
|
+
return [
|
|
161
|
+
{ role: 'system', content: prompt },
|
|
162
|
+
{ role: 'user', content: `Extract entities and relationships from these facts:\n\n${factsList}` },
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build the dedup prompt.
|
|
167
|
+
* Compares new facts against existing facts.
|
|
168
|
+
*/
|
|
169
|
+
export function buildDedupPrompt(newFacts, existingFacts) {
|
|
170
|
+
const newList = newFacts.map((f, i) => `NEW[${i}]: ${f}`).join('\n');
|
|
171
|
+
const existingList = existingFacts.map((f, i) => `EXISTING[${i}] (lineage: ${f.lineage_id}): ${f.content}`).join('\n');
|
|
172
|
+
return [
|
|
173
|
+
{ role: 'system', content: DEDUP_PROMPT },
|
|
174
|
+
{ role: 'user', content: `--- NEW FACTS ---\n${newList}\n\n--- EXISTING FACTS ---\n${existingList}` },
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
// =============================================================================
|
|
178
|
+
// LEGACY — Keep old function signature for backward compat during migration
|
|
179
|
+
// =============================================================================
|
|
180
|
+
export const EXTRACTION_SYSTEM_PROMPT = FACT_EXTRACTION_PROMPT;
|
|
181
|
+
export function buildExtractionPrompt(input, existingFacts) {
|
|
182
|
+
// Legacy: still used by the current pipeline
|
|
183
|
+
// Will be replaced by buildFactExtractionPrompt + buildGraphExtractionPrompt
|
|
184
|
+
let userContent = `Extract facts from this text:\n\n${input}`;
|
|
185
|
+
if (existingFacts && existingFacts.length > 0) {
|
|
186
|
+
const factsBlock = existingFacts
|
|
187
|
+
.map((f) => `- [lineage_id: ${f.lineage_id}] ${f.content}`)
|
|
188
|
+
.join('\n');
|
|
189
|
+
userContent += `\n\n--- EXISTING FACTS (for deduplication) ---\n${factsBlock}`;
|
|
190
|
+
}
|
|
191
|
+
return [
|
|
192
|
+
{ role: 'system', content: FACT_EXTRACTION_PROMPT },
|
|
193
|
+
{ role: 'user', content: userContent },
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["prompts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["prompts.ts"],"names":[],"mappings":"AAGA,gFAAgF;AAChF,qDAAqD;AACrD,gFAAgF;AAEhF,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CA8DK,CAAC;AAE5C,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAEhF,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAkCf,CAAC;AAEzB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAE7G,gFAAgF;AAChF,0DAA0D;AAC1D,gFAAgF;AAEhF,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;2FAmB+D,CAAC;AAW5F;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAa;IACrD,OAAO;QACL,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,sBAAsB,EAAE;QACnD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,oCAAoC,KAAK,EAAE,EAAE;KACvE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAe,EACf,WAAsB,EACtB,iBAAsC;IAEtC,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEnE,0EAA0E;IAC1E,MAAM,YAAY,GAAG,WAAW,IAAI,oBAAoB,CAAC;IACzD,MAAM,eAAe,GAAG,iBAAiB,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAChF,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;IAEzE,IAAI,MAAM,GAAG,uBAAuB,CAAC,OAAO,CAAC,gBAAgB,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAExF,wEAAwE;IACxE,IAAI,iBAAiB,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,eAAe,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAChD,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;gBACnC,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAChC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,WAAW,GAAG,CAC3E,CAAC,IAAI,CAAC,IAAI,CAAC;gBACd,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,IAAI,SAAS,EAAE,CAAC;QACvD,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,MAAM,IAAI,wCAAwC,eAAe,+NAA+N,CAAC;IACnS,CAAC;IAED,OAAO;QACL,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE;QACnC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,2DAA2D,SAAS,EAAE,EAAE;KAClG,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAkB,EAAE,aAA6B;IAChF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvH,OAAO;QACL,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;QACzC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,OAAO,+BAA+B,YAAY,EAAE,EAAE;KACtG,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,4EAA4E;AAC5E,gFAAgF;AAEhF,MAAM,CAAC,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AAE/D,MAAM,UAAU,qBAAqB,CACnC,KAAa,EACb,aAA8B;IAE9B,6CAA6C;IAC7C,6EAA6E;IAC7E,IAAI,WAAW,GAAG,oCAAoC,KAAK,EAAE,CAAC;IAC9D,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,aAAa;aAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC1D,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,WAAW,IAAI,mDAAmD,UAAU,EAAE,CAAC;IACjF,CAAC;IACD,OAAO;QACL,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,sBAAsB,EAAE;QACnD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE;KACvC,CAAC;AACJ,CAAC"}
|
|
@@ -51,12 +51,20 @@ For identity/trait facts, state them DIRECTLY:
|
|
|
51
51
|
8. For EVERY fact, include "ed" (event date) if the fact describes something that happened at a specific time.
|
|
52
52
|
- "User went to the gym on May 7" → ed: "2023-05-07"
|
|
53
53
|
- "User prefers dark mode" → ed: null (timeless preference)
|
|
54
|
-
If
|
|
54
|
+
- If only a month is mentioned (e.g., "in March 2026") and today IS in that month, use TODAY's date from the header.
|
|
55
|
+
- If only a month is mentioned and it's a PAST month, use the 15th as midpoint.
|
|
56
|
+
Set "dd" (document date) to today's date from the header for ALL facts — this is when the conversation happened.
|
|
57
|
+
|
|
58
|
+
9. PATTERN DETECTION: If the text reveals a recurring behavior, preference, routine, or coping strategy, extract it as a pattern fact with higher importance (0.7-0.9).
|
|
59
|
+
- "I always procrastinate on Mondays" → {"t": "User tends to procrastinate on Mondays", "i": 0.8, "ed": null, "dd": "...", "p": true}
|
|
60
|
+
- "Coffee helps me focus" → {"t": "User finds that coffee helps with focus", "i": 0.7, "ed": null, "dd": "...", "p": true}
|
|
61
|
+
Patterns are behavioral insights — routines, coping strategies, energy patterns, recurring struggles.
|
|
62
|
+
Mark patterns with "p": true in the output. Regular facts use "p": false.
|
|
55
63
|
|
|
56
64
|
## OUTPUT
|
|
57
65
|
|
|
58
66
|
Return ONLY a JSON object:
|
|
59
|
-
{"facts": [{"t": "fact text here", "i": 0.7, "ed": "2023-05-07", "dd": "2023-05-08"}, {"t": "another fact", "i": 0.3, "ed": null, "dd": "2023-05-08"}]}
|
|
67
|
+
{"facts": [{"t": "fact text here", "i": 0.7, "ed": "2023-05-07", "dd": "2023-05-08", "p": false}, {"t": "another fact", "i": 0.3, "ed": null, "dd": "2023-05-08", "p": false}]}
|
|
60
68
|
|
|
61
69
|
- ed (eventDate): ISO date string of WHEN the event occurred, or null if not temporal
|
|
62
70
|
- dd (documentDate): ISO date string of when the conversation took place, from context header
|
|
@@ -150,9 +158,10 @@ export interface ExistingFact {
|
|
|
150
158
|
* Simple: extract facts as strings.
|
|
151
159
|
*/
|
|
152
160
|
export function buildFactExtractionPrompt(input: string): LLMMessage[] {
|
|
161
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
153
162
|
return [
|
|
154
163
|
{ role: 'system', content: FACT_EXTRACTION_PROMPT },
|
|
155
|
-
{ role: 'user', content: `
|
|
164
|
+
{ role: 'user', content: `[Today's date: ${today}]\n\nExtract facts from this text:\n\n${input}` },
|
|
156
165
|
];
|
|
157
166
|
}
|
|
158
167
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding Window Inference Pipeline — like Hydra DB's context-enriched chunking.
|
|
3
|
+
*
|
|
4
|
+
* Splits long text into segments with overlapping context windows.
|
|
5
|
+
* Each segment gets surrounding context (lookback + lookahead) so the LLM can:
|
|
6
|
+
* - Resolve pronouns ("he" → "John")
|
|
7
|
+
* - Resolve references ("that framework" → "React")
|
|
8
|
+
* - Understand temporal context from earlier messages
|
|
9
|
+
*
|
|
10
|
+
* This prevents the "Orphaned Pronoun Paradox" where isolated chunks lose meaning.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
segmentSize: 800,
|
|
14
|
+
hPrev: 2,
|
|
15
|
+
hNext: 1,
|
|
16
|
+
minInputLength: 3500, // Window multi-turn conversations for pronoun/temporal resolution
|
|
17
|
+
maxSegments: 6, // Allow more segments for better coverage
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Split text into segments at sentence boundaries.
|
|
21
|
+
*/
|
|
22
|
+
function splitIntoSegments(text, segmentSize) {
|
|
23
|
+
const segments = [];
|
|
24
|
+
let current = '';
|
|
25
|
+
// Split by sentences (period/exclamation/question followed by space or newline)
|
|
26
|
+
const sentences = text.split(/(?<=[.!?])\s+/);
|
|
27
|
+
for (const sentence of sentences) {
|
|
28
|
+
if (current.length + sentence.length > segmentSize && current.length > 0) {
|
|
29
|
+
segments.push(current.trim());
|
|
30
|
+
current = sentence;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
current += (current ? ' ' : '') + sentence;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (current.trim())
|
|
37
|
+
segments.push(current.trim());
|
|
38
|
+
return segments;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create enriched segments with sliding window context.
|
|
42
|
+
*
|
|
43
|
+
* For each segment, includes surrounding context so the LLM can resolve
|
|
44
|
+
* references and understand temporal relationships.
|
|
45
|
+
*/
|
|
46
|
+
export function createEnrichedSegments(text, config) {
|
|
47
|
+
const c = { ...DEFAULT_CONFIG, ...config };
|
|
48
|
+
// Short inputs don't need windowing
|
|
49
|
+
if (text.length < c.minInputLength) {
|
|
50
|
+
return [{
|
|
51
|
+
segment: text,
|
|
52
|
+
contextWindow: text,
|
|
53
|
+
index: 0,
|
|
54
|
+
total: 1,
|
|
55
|
+
}];
|
|
56
|
+
}
|
|
57
|
+
const segments = splitIntoSegments(text, c.segmentSize);
|
|
58
|
+
// Cap segments to avoid excessive LLM calls
|
|
59
|
+
const effectiveSegments = segments.length > c.maxSegments
|
|
60
|
+
? segments.slice(0, c.maxSegments)
|
|
61
|
+
: segments;
|
|
62
|
+
return effectiveSegments.map((seg, i) => {
|
|
63
|
+
// Build context window with lookback and lookahead
|
|
64
|
+
const prevStart = Math.max(0, i - c.hPrev);
|
|
65
|
+
const nextEnd = Math.min(segments.length - 1, i + c.hNext);
|
|
66
|
+
const contextBefore = segments.slice(prevStart, i).join('\n');
|
|
67
|
+
const contextAfter = segments.slice(i + 1, nextEnd + 1).join('\n');
|
|
68
|
+
let contextWindow = '';
|
|
69
|
+
if (contextBefore) {
|
|
70
|
+
contextWindow += `[PRECEDING CONTEXT — use this to resolve pronouns, references, and temporal expressions in the current segment]\n${contextBefore}\n\n`;
|
|
71
|
+
}
|
|
72
|
+
contextWindow += `[CURRENT SEGMENT — extract facts from this]\n${seg}`;
|
|
73
|
+
if (contextAfter) {
|
|
74
|
+
contextWindow += `\n\n[FOLLOWING CONTEXT]\n${contextAfter}`;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
segment: seg,
|
|
78
|
+
contextWindow,
|
|
79
|
+
index: i,
|
|
80
|
+
total: effectiveSegments.length,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=sliding-window.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sliding-window.js","sourceRoot":"","sources":["sliding-window.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA0BH,MAAM,cAAc,GAA2B;IAC7C,WAAW,EAAE,GAAG;IAChB,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,cAAc,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"sliding-window.js","sourceRoot":"","sources":["sliding-window.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,6 +23,18 @@ export interface ExtractedFact {
|
|
|
23
23
|
contradictsFactId?: string;
|
|
24
24
|
/** Canonical names of entities mentioned in THIS fact (for precise fact-entity linking) */
|
|
25
25
|
entityCanonicalNames?: string[];
|
|
26
|
+
/** The conversation segment this fact was extracted from */
|
|
27
|
+
sourceChunk?: string;
|
|
28
|
+
/** When the event described in the fact actually occurred */
|
|
29
|
+
eventDate?: Date;
|
|
30
|
+
/** When the conversation/document was authored */
|
|
31
|
+
documentDate?: Date;
|
|
32
|
+
/** If this fact relates to an existing one: 'updates' | 'extends' | 'derives' */
|
|
33
|
+
relationType?: 'updates' | 'extends' | 'derives';
|
|
34
|
+
/** The ID of the existing fact this relates to (set by dedup) */
|
|
35
|
+
relatedFactId?: string;
|
|
36
|
+
/** Context-enriched version of content used for embedding (not shown to users) */
|
|
37
|
+
contextualContent?: string;
|
|
26
38
|
}
|
|
27
39
|
export interface ExtractedEntity {
|
|
28
40
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE1F,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,YAAY,CAAC;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { calculateDecayScore } from '../salience/decay.js';
|
|
2
|
+
/**
|
|
3
|
+
* Record memory accesses after a search.
|
|
4
|
+
* Also updates last_accessed and frequency on accessed facts.
|
|
5
|
+
* This is called fire-and-forget from the search orchestrator.
|
|
6
|
+
*/
|
|
7
|
+
export async function recordAccesses(storage, tenantId, query, results, config) {
|
|
8
|
+
const halfLifeDays = config?.halfLifeDays ?? 30;
|
|
9
|
+
const normalizationK = config?.normalizationK ?? 50;
|
|
10
|
+
const decayUpdates = [];
|
|
11
|
+
for (let i = 0; i < results.length; i++) {
|
|
12
|
+
const result = results[i];
|
|
13
|
+
const id = crypto.randomUUID();
|
|
14
|
+
// Create access record
|
|
15
|
+
await storage.createMemoryAccess({
|
|
16
|
+
id,
|
|
17
|
+
tenantId,
|
|
18
|
+
factId: result.fact.id,
|
|
19
|
+
query,
|
|
20
|
+
retrievalMethod: result.triggeredBy ? 'trigger' : 'fusion',
|
|
21
|
+
similarityScore: result.signals.vectorScore > 0 ? result.signals.vectorScore : undefined,
|
|
22
|
+
rankPosition: i + 1,
|
|
23
|
+
triggerId: result.triggeredBy,
|
|
24
|
+
});
|
|
25
|
+
// Recalculate decay score with updated access time and frequency
|
|
26
|
+
const newFrequency = result.fact.frequency + 1;
|
|
27
|
+
const newDecayScore = calculateDecayScore({
|
|
28
|
+
importance: result.fact.importance,
|
|
29
|
+
frequency: newFrequency,
|
|
30
|
+
lastAccessed: new Date(),
|
|
31
|
+
halfLifeDays,
|
|
32
|
+
normalizationK,
|
|
33
|
+
});
|
|
34
|
+
decayUpdates.push({
|
|
35
|
+
id: result.fact.id,
|
|
36
|
+
decayScore: newDecayScore,
|
|
37
|
+
lastAccessed: new Date(),
|
|
38
|
+
frequency: newFrequency,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Batch update decay scores (also updates last_accessed and frequency on the fact)
|
|
42
|
+
if (decayUpdates.length > 0) {
|
|
43
|
+
await storage.updateDecayScores(tenantId, decayUpdates);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Submit user feedback for a retrieved memory.
|
|
48
|
+
* Adjusts the fact's importance score based on feedback.
|
|
49
|
+
*/
|
|
50
|
+
export async function submitFeedback(storage, tenantId, factId, feedback, config) {
|
|
51
|
+
// 1. Update the memory access record with feedback
|
|
52
|
+
await storage.updateFeedback(tenantId, factId, {
|
|
53
|
+
wasUseful: feedback.wasUseful,
|
|
54
|
+
feedbackType: feedback.feedbackType,
|
|
55
|
+
feedbackDetail: feedback.feedbackDetail,
|
|
56
|
+
wasCorrected: feedback.feedbackType === 'correction',
|
|
57
|
+
});
|
|
58
|
+
// 2. Adjust fact importance based on feedback
|
|
59
|
+
const fact = await storage.getFact(tenantId, factId);
|
|
60
|
+
if (!fact)
|
|
61
|
+
return;
|
|
62
|
+
let newImportance = fact.importance;
|
|
63
|
+
switch (feedback.feedbackType) {
|
|
64
|
+
case 'explicit_positive':
|
|
65
|
+
case 'implicit_positive':
|
|
66
|
+
newImportance = Math.min(1.0, fact.importance + 0.05);
|
|
67
|
+
break;
|
|
68
|
+
case 'explicit_negative':
|
|
69
|
+
case 'implicit_negative':
|
|
70
|
+
newImportance = Math.max(0.1, fact.importance - 0.05);
|
|
71
|
+
break;
|
|
72
|
+
case 'correction':
|
|
73
|
+
newImportance = Math.max(0.1, fact.importance - 0.1);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
// 3. Update the fact's importance and recalculate decay score
|
|
77
|
+
if (newImportance !== fact.importance) {
|
|
78
|
+
const newDecayScore = calculateDecayScore({
|
|
79
|
+
importance: newImportance,
|
|
80
|
+
frequency: fact.frequency,
|
|
81
|
+
lastAccessed: fact.lastAccessed ?? new Date(),
|
|
82
|
+
halfLifeDays: config?.halfLifeDays ?? 30,
|
|
83
|
+
normalizationK: config?.normalizationK ?? 50,
|
|
84
|
+
});
|
|
85
|
+
await storage.updateDecayScores(tenantId, [
|
|
86
|
+
{ id: factId, decayScore: newDecayScore, importance: newImportance },
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=tracker.js.map
|
package/src/models/api-key.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export declare const ApiKeySchema: z.ZodObject<{
|
|
|
12
12
|
createdAt: z.ZodDate;
|
|
13
13
|
}, "strip", z.ZodTypeAny, {
|
|
14
14
|
name: string;
|
|
15
|
-
id: string;
|
|
16
15
|
active: boolean;
|
|
16
|
+
id: string;
|
|
17
17
|
tenantId: string;
|
|
18
18
|
createdAt: Date;
|
|
19
19
|
keyHash: string;
|
|
@@ -23,8 +23,8 @@ export declare const ApiKeySchema: z.ZodObject<{
|
|
|
23
23
|
lastUsedAt: Date | null;
|
|
24
24
|
}, {
|
|
25
25
|
name: string;
|
|
26
|
-
id: string;
|
|
27
26
|
active: boolean;
|
|
27
|
+
id: string;
|
|
28
28
|
tenantId: string;
|
|
29
29
|
createdAt: Date;
|
|
30
30
|
keyHash: string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { API_KEY_SCOPES } from '../config.js';
|
|
3
|
+
export const ApiKeySchema = z.object({
|
|
4
|
+
id: z.string().uuid(),
|
|
5
|
+
tenantId: z.string().uuid(),
|
|
6
|
+
keyHash: z.string(),
|
|
7
|
+
keyPrefix: z.string(),
|
|
8
|
+
name: z.string().max(100),
|
|
9
|
+
scopes: z.array(z.enum(API_KEY_SCOPES)),
|
|
10
|
+
expiresAt: z.coerce.date().nullable(),
|
|
11
|
+
lastUsedAt: z.coerce.date().nullable(),
|
|
12
|
+
active: z.boolean(),
|
|
13
|
+
createdAt: z.coerce.date(),
|
|
14
|
+
});
|
|
15
|
+
export const CreateApiKeySchema = z.object({
|
|
16
|
+
tenantId: z.string().uuid(),
|
|
17
|
+
name: z.string().max(100).default('Default'),
|
|
18
|
+
scopes: z.array(z.enum(API_KEY_SCOPES)).default(['read', 'write']),
|
|
19
|
+
expiresAt: z.coerce.date().optional(),
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=api-key.js.map
|
package/src/models/edge.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const EdgeSchema: z.ZodObject<{
|
|
|
5
5
|
sourceId: z.ZodString;
|
|
6
6
|
targetId: z.ZodString;
|
|
7
7
|
relation: z.ZodString;
|
|
8
|
-
edgeType: z.ZodEnum<["associative", "causal", "temporal", "contradictory", "hierarchical"]>;
|
|
8
|
+
edgeType: z.ZodEnum<["associative", "causal", "temporal", "contradictory", "hierarchical", "updates", "extends", "derives"]>;
|
|
9
9
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
10
10
|
validFrom: z.ZodDate;
|
|
11
11
|
validUntil: z.ZodNullable<z.ZodDate>;
|
|
@@ -24,7 +24,7 @@ export declare const EdgeSchema: z.ZodObject<{
|
|
|
24
24
|
sourceId: string;
|
|
25
25
|
targetId: string;
|
|
26
26
|
relation: string;
|
|
27
|
-
edgeType: "
|
|
27
|
+
edgeType: "temporal" | "associative" | "causal" | "contradictory" | "hierarchical" | "updates" | "extends" | "derives";
|
|
28
28
|
weight: number;
|
|
29
29
|
factId: string | null;
|
|
30
30
|
}, {
|
|
@@ -38,7 +38,7 @@ export declare const EdgeSchema: z.ZodObject<{
|
|
|
38
38
|
sourceId: string;
|
|
39
39
|
targetId: string;
|
|
40
40
|
relation: string;
|
|
41
|
-
edgeType: "
|
|
41
|
+
edgeType: "temporal" | "associative" | "causal" | "contradictory" | "hierarchical" | "updates" | "extends" | "derives";
|
|
42
42
|
factId: string | null;
|
|
43
43
|
weight?: number | undefined;
|
|
44
44
|
}>;
|
|
@@ -48,7 +48,7 @@ export declare const CreateEdgeSchema: z.ZodObject<{
|
|
|
48
48
|
sourceId: z.ZodString;
|
|
49
49
|
targetId: z.ZodString;
|
|
50
50
|
relation: z.ZodString;
|
|
51
|
-
edgeType: z.ZodEnum<["associative", "causal", "temporal", "contradictory", "hierarchical"]>;
|
|
51
|
+
edgeType: z.ZodEnum<["associative", "causal", "temporal", "contradictory", "hierarchical", "updates", "extends", "derives"]>;
|
|
52
52
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
53
53
|
factId: z.ZodOptional<z.ZodString>;
|
|
54
54
|
confidence: z.ZodDefault<z.ZodNumber>;
|
|
@@ -60,7 +60,7 @@ export declare const CreateEdgeSchema: z.ZodObject<{
|
|
|
60
60
|
sourceId: string;
|
|
61
61
|
targetId: string;
|
|
62
62
|
relation: string;
|
|
63
|
-
edgeType: "
|
|
63
|
+
edgeType: "temporal" | "associative" | "causal" | "contradictory" | "hierarchical" | "updates" | "extends" | "derives";
|
|
64
64
|
weight: number;
|
|
65
65
|
factId?: string | undefined;
|
|
66
66
|
}, {
|
|
@@ -68,7 +68,7 @@ export declare const CreateEdgeSchema: z.ZodObject<{
|
|
|
68
68
|
sourceId: string;
|
|
69
69
|
targetId: string;
|
|
70
70
|
relation: string;
|
|
71
|
-
edgeType: "
|
|
71
|
+
edgeType: "temporal" | "associative" | "causal" | "contradictory" | "hierarchical" | "updates" | "extends" | "derives";
|
|
72
72
|
confidence?: number | undefined;
|
|
73
73
|
metadata?: Record<string, unknown> | undefined;
|
|
74
74
|
weight?: number | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { unitFloat, EDGE_TYPES } from '../config.js';
|
|
3
|
+
export const EdgeSchema = z.object({
|
|
4
|
+
id: z.string().uuid(),
|
|
5
|
+
tenantId: z.string().uuid(),
|
|
6
|
+
sourceId: z.string().uuid(),
|
|
7
|
+
targetId: z.string().uuid(),
|
|
8
|
+
relation: z.string(),
|
|
9
|
+
edgeType: z.enum(EDGE_TYPES),
|
|
10
|
+
weight: z.number().min(0).default(1.0),
|
|
11
|
+
validFrom: z.coerce.date(),
|
|
12
|
+
validUntil: z.coerce.date().nullable(),
|
|
13
|
+
factId: z.string().uuid().nullable(),
|
|
14
|
+
confidence: unitFloat,
|
|
15
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
16
|
+
createdAt: z.coerce.date(),
|
|
17
|
+
});
|
|
18
|
+
export const CreateEdgeSchema = z.object({
|
|
19
|
+
tenantId: z.string().uuid(),
|
|
20
|
+
sourceId: z.string().uuid(),
|
|
21
|
+
targetId: z.string().uuid(),
|
|
22
|
+
relation: z.string(),
|
|
23
|
+
edgeType: z.enum(EDGE_TYPES),
|
|
24
|
+
weight: z.number().min(0).default(1.0),
|
|
25
|
+
factId: z.string().uuid().optional(),
|
|
26
|
+
confidence: unitFloat.default(0.8),
|
|
27
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=edge.js.map
|
package/src/models/entity.d.ts
CHANGED
|
@@ -13,9 +13,9 @@ export declare const EntitySchema: z.ZodObject<{
|
|
|
13
13
|
updatedAt: z.ZodDate;
|
|
14
14
|
}, "strip", z.ZodTypeAny, {
|
|
15
15
|
name: string;
|
|
16
|
-
id: string;
|
|
17
16
|
embeddingModel: string | null;
|
|
18
17
|
embeddingDim: number | null;
|
|
18
|
+
id: string;
|
|
19
19
|
tenantId: string;
|
|
20
20
|
createdAt: Date;
|
|
21
21
|
entityType: string;
|
|
@@ -25,9 +25,9 @@ export declare const EntitySchema: z.ZodObject<{
|
|
|
25
25
|
updatedAt: Date;
|
|
26
26
|
}, {
|
|
27
27
|
name: string;
|
|
28
|
-
id: string;
|
|
29
28
|
embeddingModel: string | null;
|
|
30
29
|
embeddingDim: number | null;
|
|
30
|
+
id: string;
|
|
31
31
|
tenantId: string;
|
|
32
32
|
createdAt: Date;
|
|
33
33
|
entityType: string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const EntitySchema = z.object({
|
|
3
|
+
id: z.string().uuid(),
|
|
4
|
+
tenantId: z.string().uuid(),
|
|
5
|
+
name: z.string().min(1).max(500),
|
|
6
|
+
entityType: z.string().min(1),
|
|
7
|
+
canonicalName: z.string().min(1),
|
|
8
|
+
properties: z.record(z.string(), z.unknown()),
|
|
9
|
+
embeddingModel: z.string().nullable(),
|
|
10
|
+
embeddingDim: z.number().int().positive().nullable(),
|
|
11
|
+
mergeTargetId: z.string().uuid().nullable(),
|
|
12
|
+
createdAt: z.coerce.date(),
|
|
13
|
+
updatedAt: z.coerce.date(),
|
|
14
|
+
});
|
|
15
|
+
export const CreateEntitySchema = z.object({
|
|
16
|
+
tenantId: z.string().uuid(),
|
|
17
|
+
name: z.string().min(1).max(500),
|
|
18
|
+
entityType: z.string().min(1),
|
|
19
|
+
canonicalName: z.string().min(1),
|
|
20
|
+
properties: z.record(z.string(), z.unknown()).default({}),
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=entity.js.map
|