@pyxmate/memory 0.13.0 → 0.15.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/{chunk-DZZHJ66P.mjs → chunk-K7JZXUBN.mjs} +1 -1
- package/dist/chunk-W4LB326D.mjs +618 -0
- package/dist/dashboard.mjs +2 -2
- package/dist/index.d.ts +158 -33
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +2 -2
- package/package.json +1 -1
- package/skills/pyx-memory/patterns/access-control.md +1 -1
- package/skills/pyx-memory/patterns/consumer.md +10 -4
- package/skills/pyx-memory/patterns/file-uploads.md +1 -1
- package/skills/pyx-memory/reference/http-api.md +46 -41
- package/skills/pyx-memory/reference/sdk-guide.md +23 -15
- package/skills/pyx-memory/reference/types.md +5 -7
- package/dist/chunk-4YIKI2BA.mjs +0 -404
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StoreInput as StoreInput$1, MemoryEntry as MemoryEntry$1, MemorySearchParams as MemorySearchParams$1, MemorySearchResult as MemorySearchResult$1, MemoryType as MemoryType$1, MemoryStats as MemoryStats$1, ExtractedImageMeta, IngestEntity as IngestEntity$1, IngestRelationship as IngestRelationship$1,
|
|
1
|
+
import { StoreInput as StoreInput$1, MemoryEntry as MemoryEntry$1, MemorySearchParams as MemorySearchParams$1, MemorySearchResult as MemorySearchResult$1, MemoryType as MemoryType$1, MemoryStats as MemoryStats$1, ExtractedImageMeta as ExtractedImageMeta$1, IngestEntity as IngestEntity$1, IngestRelationship as IngestRelationship$1, IngestEvent as IngestEvent$1, GraphNode as GraphNode$1, GraphTraversalResult as GraphTraversalResult$1 } from '@pyx-memory/shared';
|
|
2
2
|
|
|
3
3
|
/** Parameters for paginated entry listing. */
|
|
4
4
|
interface MemoryListParams {
|
|
@@ -72,15 +72,14 @@ interface ExtendedMemoryInterface extends MemoryInterface {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Callbacks for two-phase file enrichment. All callbacks are optional
|
|
76
|
-
* SDK can support every combination of the v2 flow:
|
|
75
|
+
* Callbacks for two-phase file enrichment. All callbacks are optional:
|
|
77
76
|
* - Image-rich PDF + describeImage only → describes images, no entity extraction
|
|
78
|
-
* - Image-rich PDF + describeImage +
|
|
79
|
-
* - Text-only file +
|
|
80
|
-
* - Mixed file +
|
|
77
|
+
* - Image-rich PDF + describeImage + extractEntitiesV2 → describes + extracts
|
|
78
|
+
* - Text-only file + extractEntitiesV2 only → extracts from textWindows
|
|
79
|
+
* - Mixed file + both → describes images + extracts from both sources
|
|
81
80
|
*
|
|
82
|
-
* Without
|
|
83
|
-
*
|
|
81
|
+
* Without any callback, the ingest stream emits the server result with no
|
|
82
|
+
* SDK-side enrichment — caller opted out entirely.
|
|
84
83
|
*/
|
|
85
84
|
interface EnrichmentCallbacks {
|
|
86
85
|
/**
|
|
@@ -88,23 +87,12 @@ interface EnrichmentCallbacks {
|
|
|
88
87
|
* metadata. Required for image-bearing files; safe to omit for text-only
|
|
89
88
|
* uploads where the server emits zero images.
|
|
90
89
|
*/
|
|
91
|
-
describeImage?: (imageBuffer: ArrayBuffer, meta: ExtractedImageMeta) => Promise<string>;
|
|
90
|
+
describeImage?: (imageBuffer: ArrayBuffer, meta: ExtractedImageMeta$1) => Promise<string>;
|
|
92
91
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*/
|
|
98
|
-
extractEntities?: (descriptions: string[]) => Promise<{
|
|
99
|
-
entities: IngestEntity$1[];
|
|
100
|
-
relationships: IngestRelationship$1[];
|
|
101
|
-
}>;
|
|
102
|
-
/**
|
|
103
|
-
* v2 entity-extraction callback. Receives text windows and image
|
|
104
|
-
* descriptions separately so callers can apply different prompts per
|
|
105
|
-
* source. Takes precedence over {@link extractEntities} when both are
|
|
106
|
-
* defined. Triggers the `X-Pyx-Enrichment-Capabilities: text_windows_v1`
|
|
107
|
-
* negotiation header on ingest.
|
|
92
|
+
* Entity-extraction callback. Receives text windows and image descriptions
|
|
93
|
+
* separately so callers can apply different prompts per source. Triggers
|
|
94
|
+
* the `X-Pyx-Enrichment-Capabilities: text_windows_v1` negotiation header
|
|
95
|
+
* on ingest.
|
|
108
96
|
*/
|
|
109
97
|
extractEntitiesV2?: (input: {
|
|
110
98
|
textWindows: string[];
|
|
@@ -116,6 +104,15 @@ interface EnrichmentCallbacks {
|
|
|
116
104
|
relationships: IngestRelationship$1[];
|
|
117
105
|
}>;
|
|
118
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Options for {@link MemoryClient.ingestFileEvents}. `signal` lets
|
|
109
|
+
* long-running ingests (LLM enrichment + graph writes) be cancelled cleanly.
|
|
110
|
+
*/
|
|
111
|
+
interface IngestFileOptions {
|
|
112
|
+
description?: string;
|
|
113
|
+
enrichment?: EnrichmentCallbacks;
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
}
|
|
119
116
|
/** Error thrown by MemoryClient when the server returns a non-success response. */
|
|
120
117
|
declare class MemoryServerError extends Error {
|
|
121
118
|
readonly status: number;
|
|
@@ -161,16 +158,43 @@ declare class MemoryClient implements ExtendedMemoryInterface {
|
|
|
161
158
|
shutdown(): Promise<void>;
|
|
162
159
|
list(params?: MemoryListParams): Promise<MemoryListResult>;
|
|
163
160
|
/**
|
|
164
|
-
*
|
|
161
|
+
* Native streaming file ingest. Yields typed {@link IngestEvent}s as the
|
|
162
|
+
* server (parsing/storing) and the SDK (enrichment/result) make progress.
|
|
163
|
+
*
|
|
164
|
+
* Wire contract: SDK POSTs with `Accept: application/x-ndjson`; the server
|
|
165
|
+
* MUST respond with NDJSON. There is no JSON fallback — older servers
|
|
166
|
+
* that emit `application/json` for this endpoint are not supported, and
|
|
167
|
+
* the SDK yields a terminal `error` event in that case.
|
|
165
168
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
+
* After the server's terminal `result`, the SDK runs its own enrichment
|
|
170
|
+
* phase (image-describe → entity-extract → `/enrich` POST), emitting
|
|
171
|
+
* progress + heartbeat events around each step, then yields the single
|
|
172
|
+
* terminal `result` event with the merged SDK + server result.
|
|
173
|
+
*
|
|
174
|
+
* Promise-shaped consumers should iterate the returned AsyncIterable and
|
|
175
|
+
* collect the terminal event; there is no separate `ingestFile()` Promise
|
|
176
|
+
* method by design (one wire format, one SDK method).
|
|
177
|
+
*/
|
|
178
|
+
ingestFileEvents(file: File, options?: IngestFileOptions): AsyncIterable<IngestEvent$1>;
|
|
179
|
+
/**
|
|
180
|
+
* Run the SDK-side enrichment phase (image-describe → entity-extract →
|
|
181
|
+
* `/enrich` POST) for a server result, emitting progress + heartbeat
|
|
182
|
+
* events around the slow steps and yielding the single terminal
|
|
183
|
+
* {@link IngestResultEvent} at the end. Skips work cleanly when the
|
|
184
|
+
* server emitted no enrichment block or the caller wired no callbacks.
|
|
185
|
+
*/
|
|
186
|
+
private completeIngestFileEvents;
|
|
187
|
+
/**
|
|
188
|
+
* Race a Promise against a periodic heartbeat tick. Yields a heartbeat
|
|
189
|
+
* IngestEvent every {@link INGEST_EVENT_HEARTBEAT_MS} until the promise
|
|
190
|
+
* settles, then returns the resolved value (or rethrows). Lets callers
|
|
191
|
+
* keep upstream sockets alive through long LLM/HTTP work without
|
|
192
|
+
* coupling the heartbeat cadence to the work itself.
|
|
169
193
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
194
|
+
private withSdkHeartbeats;
|
|
195
|
+
private normalizeActiveIngestStage;
|
|
196
|
+
private fileIngestResultFromEvent;
|
|
197
|
+
private ingestErrorEvent;
|
|
174
198
|
/**
|
|
175
199
|
* Get the download URL for an uploaded file.
|
|
176
200
|
* Returns a URL that serves the original file binary with proper Content-Type.
|
|
@@ -492,4 +516,105 @@ interface GraphTraversalResult {
|
|
|
492
516
|
}>;
|
|
493
517
|
}
|
|
494
518
|
|
|
495
|
-
|
|
519
|
+
/** Metadata for a single image extracted from a PDF. */
|
|
520
|
+
interface ExtractedImageMeta {
|
|
521
|
+
imageId: string;
|
|
522
|
+
pageNumber: number;
|
|
523
|
+
width: number;
|
|
524
|
+
height: number;
|
|
525
|
+
mimeType: 'image/png' | 'image/jpeg';
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Legacy Phase 1 enrichment block (v1). Returned by servers running the
|
|
529
|
+
* pre-Patch-#2 pipeline OR by Patch-#2+ servers when the client does NOT
|
|
530
|
+
* signal `text_windows_v1` capability via the
|
|
531
|
+
* `X-Pyx-Enrichment-Capabilities` request header.
|
|
532
|
+
*/
|
|
533
|
+
interface EnrichmentPendingV1 {
|
|
534
|
+
fileId: string;
|
|
535
|
+
token: string;
|
|
536
|
+
expiresAt: string;
|
|
537
|
+
images: ExtractedImageMeta[];
|
|
538
|
+
}
|
|
539
|
+
/** Truncation report for v2 text-window emission. Always present in v2. */
|
|
540
|
+
interface EnrichmentTextWindowsTruncation {
|
|
541
|
+
/** Total characters seen by the parser, including content beyond the cap. */
|
|
542
|
+
originalChars: number;
|
|
543
|
+
/** Sum of emitted window lengths. <= originalChars. */
|
|
544
|
+
includedChars: number;
|
|
545
|
+
windowCount: number;
|
|
546
|
+
truncated: boolean;
|
|
547
|
+
reason: 'maxWindows' | 'maxTotalChars' | null;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* v2 enrichment block. Returned by Patch-#2+ servers when the client signals
|
|
551
|
+
* `text_windows_v1` capability. Adds `textWindows` so SDK callers that bring
|
|
552
|
+
* their own LLM can extract entities from any text-bearing file (text PDFs,
|
|
553
|
+
* .txt, .md, .docx, .pptx, .xlsx, …) — closing the structural gap that left
|
|
554
|
+
* Knowledge Graph + Vector Map blank for text-only uploads.
|
|
555
|
+
*/
|
|
556
|
+
interface EnrichmentPendingV2 {
|
|
557
|
+
/** Discriminator. v1 omits this field. */
|
|
558
|
+
version: 2;
|
|
559
|
+
fileId: string;
|
|
560
|
+
token: string;
|
|
561
|
+
expiresAt: string;
|
|
562
|
+
images: ExtractedImageMeta[];
|
|
563
|
+
/** UTF-16 text windows for entity extraction. Empty when no text content. */
|
|
564
|
+
textWindows: string[];
|
|
565
|
+
textWindowsTruncation: EnrichmentTextWindowsTruncation;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Discriminated union over enrichment shapes. Narrow with `'version' in prep`
|
|
569
|
+
* before accessing v2-only fields.
|
|
570
|
+
*/
|
|
571
|
+
type EnrichmentPending = EnrichmentPendingV1 | EnrichmentPendingV2;
|
|
572
|
+
/** Extended ingestion result when PDF contains images or v2 text windows. */
|
|
573
|
+
interface FileIngestResult {
|
|
574
|
+
filename: string;
|
|
575
|
+
fileType: string;
|
|
576
|
+
chunks: number;
|
|
577
|
+
entryIds: string[];
|
|
578
|
+
totalCharacters: number;
|
|
579
|
+
/** Present when images were extracted (v1) OR text windows / images were emitted (v2). */
|
|
580
|
+
enrichment?: EnrichmentPending;
|
|
581
|
+
}
|
|
582
|
+
/** Coarse pipeline stages. Stable vocabulary — finer detail goes in counters/message. */
|
|
583
|
+
type IngestStage = 'parsing' | 'storing' | 'enrichment' | 'complete';
|
|
584
|
+
/** Mid-stream progress notification — never carries the terminal result. */
|
|
585
|
+
interface IngestProgressEvent {
|
|
586
|
+
schemaVersion: 1;
|
|
587
|
+
type: 'progress';
|
|
588
|
+
stage: Exclude<IngestStage, 'complete'>;
|
|
589
|
+
filename?: string;
|
|
590
|
+
chunksStored?: number;
|
|
591
|
+
totalCharacters?: number;
|
|
592
|
+
message?: string;
|
|
593
|
+
}
|
|
594
|
+
/** Keepalive emitted while a stage is doing slow work (LLM, graph batch, etc). */
|
|
595
|
+
interface IngestHeartbeatEvent {
|
|
596
|
+
schemaVersion: 1;
|
|
597
|
+
type: 'heartbeat';
|
|
598
|
+
stage: Exclude<IngestStage, 'complete'>;
|
|
599
|
+
message?: string;
|
|
600
|
+
}
|
|
601
|
+
/** Terminal success — exactly one per stream, carries the full FileIngestResult. */
|
|
602
|
+
interface IngestResultEvent extends FileIngestResult {
|
|
603
|
+
schemaVersion: 1;
|
|
604
|
+
type: 'result';
|
|
605
|
+
stage: 'complete';
|
|
606
|
+
message?: string;
|
|
607
|
+
}
|
|
608
|
+
/** Terminal failure — exactly one per stream, mutually exclusive with result. */
|
|
609
|
+
interface IngestErrorEvent {
|
|
610
|
+
schemaVersion: 1;
|
|
611
|
+
type: 'error';
|
|
612
|
+
stage: IngestStage;
|
|
613
|
+
error: string;
|
|
614
|
+
message?: string;
|
|
615
|
+
code?: string | number;
|
|
616
|
+
status?: number;
|
|
617
|
+
}
|
|
618
|
+
type IngestEvent = IngestProgressEvent | IngestHeartbeatEvent | IngestResultEvent | IngestErrorEvent;
|
|
619
|
+
|
|
620
|
+
export { type AgentId, type ApiResponse, type ConsolidationRunResult, DEFAULTS, EmbeddingProviderName, type EnrichmentCallbacks, type ExtendedMemoryInterface, type GraphFailureMode, type GraphNode, type GraphRelationship, type GraphTraversalResult, type IngestEntity, type IngestErrorEvent, type IngestEvent, type IngestFileOptions, type IngestHeartbeatEvent, type IngestProgressEvent, type IngestRelationship, type IngestResultEvent, type IngestStage, type IngestionResult, MemoryClient, type MemoryClientOptions, type MemoryEntry, type MemoryIngestRequest, type MemoryInterface, type MemoryListParams, type MemoryListResult, type MemorySearchParams, type MemorySearchResult, MemoryServerError, type MemoryStats, MemoryType, RAGStrategy, SensitivityLevel, type StoreInput, StoreTarget, type TemporalQueryFilters, type TenantScopeOptions, type Timestamp, VectorProvider };
|
package/dist/index.mjs
CHANGED
package/dist/react.mjs
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
toGraphologyFormat,
|
|
12
12
|
transformGraphData,
|
|
13
13
|
unreachableHealth
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-K7JZXUBN.mjs";
|
|
15
|
+
import "./chunk-W4LB326D.mjs";
|
|
16
16
|
|
|
17
17
|
// ../dashboard/src/hooks/use-consolidation-log.ts
|
|
18
18
|
import { useCallback as useCallback2, useMemo } from "react";
|
package/package.json
CHANGED
|
@@ -207,7 +207,7 @@ pyx-memory has two auth tiers: `API_KEY` (read + write) and `ADMIN_API_KEY` (des
|
|
|
207
207
|
|
|
208
208
|
| Key | Allowed operations |
|
|
209
209
|
|-----|-------------------|
|
|
210
|
-
| `API_KEY` | All read operations (search, get, list, stats) + write operations (store,
|
|
210
|
+
| `API_KEY` | All read operations (search, get, list, stats) + write operations (store, ingestFileEvents) |
|
|
211
211
|
| `ADMIN_API_KEY` | Destructive operations: DELETE, forget, decay, consolidate, reindex, deleteBySource |
|
|
212
212
|
|
|
213
213
|
```bash
|
|
@@ -42,18 +42,24 @@ if (memory) {
|
|
|
42
42
|
const nodes = await memory.graphNodes();
|
|
43
43
|
const traversal = await memory.graphQuery({ nodeId: 'node-1', depth: 2 });
|
|
44
44
|
|
|
45
|
-
// File ingestion —
|
|
45
|
+
// File ingestion — native NDJSON streaming via ingestFileEvents()
|
|
46
46
|
const file = new File([buffer], 'report.pdf', { type: 'application/pdf' });
|
|
47
|
-
const
|
|
47
|
+
for await (const event of memory.ingestFileEvents(file)) {
|
|
48
|
+
if (event.type === 'progress') console.log(`[${event.stage}] ${event.message ?? ''}`);
|
|
49
|
+
if (event.type === 'error') throw new Error(event.message ?? event.error);
|
|
50
|
+
// event.type === 'result' carries the terminal { filename, chunks, entryIds, ... }
|
|
51
|
+
}
|
|
48
52
|
|
|
49
53
|
// Image ingestion with AI description
|
|
50
54
|
// pyx-memory saves the original image to {DATA_DIR}/files/ and indexes the
|
|
51
55
|
// description in vector + SQLite for semantic search. Without a description,
|
|
52
56
|
// images get a minimal placeholder ("[Image] filename (size KB)").
|
|
53
57
|
const image = new File([imgBuffer], 'photo.png', { type: 'image/png' });
|
|
54
|
-
const
|
|
58
|
+
for await (const _ of memory.ingestFileEvents(image, {
|
|
55
59
|
description: 'A screenshot of the login page showing an error dialog',
|
|
56
|
-
})
|
|
60
|
+
})) {
|
|
61
|
+
/* drain to terminal */
|
|
62
|
+
}
|
|
57
63
|
|
|
58
64
|
// Lifecycle
|
|
59
65
|
await memory.consolidate();
|
|
@@ -90,53 +90,42 @@ For deterministic peak memory or timing (production UX), consumers should pre-ex
|
|
|
90
90
|
|
|
91
91
|
**Image ingestion**: Images cannot have text extracted. Pass a `description` field with a natural-language description (e.g., from an LLM with vision). Without a description, images get a minimal placeholder (`[Image] filename (size KB)`).
|
|
92
92
|
|
|
93
|
-
**PDF enrichment**: When a PDF contains images (≥50x50px), the
|
|
93
|
+
**PDF enrichment**: When a PDF contains images (≥50x50px), the server emits an `enrichment` block on the terminal `result` event. The SDK's `ingestFileEvents()` with `EnrichmentCallbacks` handles the full flow automatically — see [sdk-guide.md](sdk-guide.md#two-phase-pdf-enrichment).
|
|
94
94
|
|
|
95
|
-
###
|
|
95
|
+
### NDJSON Wire Format (the only response shape)
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
# Via Accept header
|
|
101
|
-
curl -X POST {{ENDPOINT}}/api/memory/ingest/file \
|
|
102
|
-
-H "Authorization: Bearer {{API_KEY}}" \
|
|
103
|
-
-H "Accept: application/x-ndjson" \
|
|
104
|
-
-F "file=@large-report.pdf"
|
|
105
|
-
|
|
106
|
-
# Via query parameter
|
|
107
|
-
curl -X POST "{{ENDPOINT}}/api/memory/ingest/file?stream=true" \
|
|
108
|
-
-H "Authorization: Bearer {{API_KEY}}" \
|
|
109
|
-
-F "file=@large-report.pdf"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
The response is streamed as newline-delimited JSON (`Content-Type: application/x-ndjson`). Each line is a JSON object:
|
|
97
|
+
`POST /api/memory/ingest/file` always streams `Content-Type: application/x-ndjson`. Each line is one typed event with `schemaVersion: 1`. The terminal event is exactly one of `result` (success) or `error` (failure):
|
|
113
98
|
|
|
114
99
|
```
|
|
115
|
-
{"type":"progress","stage":"parsing","filename":"large-report.pdf","message":"Extracting text..."}
|
|
116
|
-
{"type":"progress","stage":"storing","filename":"large-report.pdf","chunksStored":10,"totalCharacters":4800
|
|
117
|
-
{"
|
|
118
|
-
{"type":"progress","stage":"
|
|
119
|
-
{"type":"result","filename":"large-report.pdf","fileType":".pdf","chunks":24,"entryIds":[
|
|
100
|
+
{"schemaVersion":1,"type":"progress","stage":"parsing","filename":"large-report.pdf","message":"Extracting text..."}
|
|
101
|
+
{"schemaVersion":1,"type":"progress","stage":"storing","filename":"large-report.pdf","chunksStored":10,"totalCharacters":4800}
|
|
102
|
+
{"schemaVersion":1,"type":"heartbeat","stage":"storing","message":"File ingest still running"}
|
|
103
|
+
{"schemaVersion":1,"type":"progress","stage":"enrichment","filename":"large-report.pdf"}
|
|
104
|
+
{"schemaVersion":1,"type":"result","stage":"complete","filename":"large-report.pdf","fileType":".pdf","chunks":24,"entryIds":["…"],"totalCharacters":11520}
|
|
120
105
|
```
|
|
121
106
|
|
|
122
|
-
**
|
|
107
|
+
**Stable stages**: `parsing` (text/image extraction), `storing` (chunk + vector writes), `enrichment` (LLM image-describe + entity-extract + `/enrich` POST), `complete` (terminal result only). Finer detail goes in optional counters (`chunksStored`, `totalCharacters`) and `message`, not in new stage names.
|
|
108
|
+
|
|
109
|
+
**Heartbeat**: emitted every 20s while the pipeline is doing slow work. Sized below undici's default 300s `headersTimeout` and Envoy's default 5m stream-idle so an LLM call or graph batch never trips an upstream socket timeout.
|
|
123
110
|
|
|
124
|
-
|
|
111
|
+
**Errors before the stream starts** (multipart parse, file size cap, validation) come back as a single-line NDJSON `error` event with the appropriate HTTP status code on the response.
|
|
125
112
|
|
|
126
|
-
|
|
113
|
+
Pre-v0.15.0 servers returned `application/json` for this endpoint. v0.15.0 SDK consumers will see a terminal `error` event with the message `Memory server returned application/json instead of application/x-ndjson — server is older than v0.15.0` if they hit such a server; upgrade the server.
|
|
127
114
|
|
|
128
115
|
### Example: ingest a document
|
|
129
116
|
|
|
130
117
|
```bash
|
|
131
|
-
curl -X POST {{ENDPOINT}}/api/memory/ingest/file \
|
|
118
|
+
curl -N -X POST {{ENDPOINT}}/api/memory/ingest/file \
|
|
132
119
|
-H "Authorization: Bearer {{API_KEY}}" \
|
|
133
120
|
-F "file=@report.pdf"
|
|
134
121
|
```
|
|
135
122
|
|
|
123
|
+
`-N` disables curl buffering so events appear as the server emits them.
|
|
124
|
+
|
|
136
125
|
### Example: ingest an image with description
|
|
137
126
|
|
|
138
127
|
```bash
|
|
139
|
-
curl -X POST {{ENDPOINT}}/api/memory/ingest/file \
|
|
128
|
+
curl -N -X POST {{ENDPOINT}}/api/memory/ingest/file \
|
|
140
129
|
-H "Authorization: Bearer {{API_KEY}}" \
|
|
141
130
|
-F "file=@screenshot.png" \
|
|
142
131
|
-F "description=A screenshot of the dashboard showing memory usage at 85%"
|
|
@@ -145,29 +134,45 @@ curl -X POST {{ENDPOINT}}/api/memory/ingest/file \
|
|
|
145
134
|
### SDK usage
|
|
146
135
|
|
|
147
136
|
```typescript
|
|
137
|
+
import { MemoryClient, type FileIngestResult } from '@pyxmate/memory';
|
|
138
|
+
|
|
139
|
+
// Iterate the AsyncIterable. The terminal event is `result` (success) or `error` (failure).
|
|
140
|
+
async function ingestAndCollect(memory: MemoryClient, file: File): Promise<FileIngestResult> {
|
|
141
|
+
for await (const event of memory.ingestFileEvents(file)) {
|
|
142
|
+
if (event.type === 'progress' || event.type === 'heartbeat') continue;
|
|
143
|
+
if (event.type === 'error') throw new Error(event.message ?? event.error);
|
|
144
|
+
// event.type === 'result'
|
|
145
|
+
const { schemaVersion: _v, type: _t, stage: _s, message: _m, ...result } = event;
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
throw new Error('memory ingest stream ended without a terminal event');
|
|
149
|
+
}
|
|
150
|
+
|
|
148
151
|
// Document (text-only)
|
|
149
152
|
const doc = new File([buffer], 'report.pdf', { type: 'application/pdf' });
|
|
150
|
-
await memory
|
|
153
|
+
await ingestAndCollect(memory, doc);
|
|
151
154
|
|
|
152
155
|
// Image with description
|
|
153
156
|
const img = new File([imgBuffer], 'photo.png', { type: 'image/png' });
|
|
154
|
-
await memory
|
|
157
|
+
await ingestAndCollect(memory, img); // pass via options when needed
|
|
158
|
+
for await (const _ of memory.ingestFileEvents(img, {
|
|
155
159
|
description: 'A photo of the whiteboard from the architecture meeting',
|
|
156
|
-
})
|
|
160
|
+
})) { /* drain */ }
|
|
157
161
|
|
|
158
162
|
// PDF with two-phase enrichment (images described via LLM vision)
|
|
159
|
-
await memory.
|
|
163
|
+
for await (const event of memory.ingestFileEvents(pdfFile, {
|
|
160
164
|
enrichment: {
|
|
161
|
-
describeImage: async (buffer, meta) =>
|
|
162
|
-
|
|
163
|
-
return 'A bar chart showing Q4 revenue growth of 23%';
|
|
164
|
-
},
|
|
165
|
-
extractEntities: async (descriptions) => ({
|
|
165
|
+
describeImage: async (buffer, meta) => 'A bar chart showing Q4 revenue growth of 23%',
|
|
166
|
+
extractEntitiesV2: async ({ textWindows, imageDescriptions, filename, mimeType }) => ({
|
|
166
167
|
entities: [{ name: 'Q4 Revenue', type: 'METRIC' }],
|
|
167
168
|
relationships: [],
|
|
168
169
|
}),
|
|
169
170
|
},
|
|
170
|
-
})
|
|
171
|
+
})) {
|
|
172
|
+
if (event.type === 'progress') {
|
|
173
|
+
console.log(`[${event.stage}] ${event.message ?? ''}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
171
176
|
```
|
|
172
177
|
|
|
173
178
|
## File Downloads
|
|
@@ -201,13 +206,13 @@ const buffer = await response.arrayBuffer();
|
|
|
201
206
|
|
|
202
207
|
## Two-Phase PDF Enrichment
|
|
203
208
|
|
|
204
|
-
When a PDF contains extractable images, the server
|
|
209
|
+
When a PDF contains extractable images, the server's terminal `result` event carries an `enrichment` block. The SDK's `ingestFileEvents()` with `EnrichmentCallbacks` runs the full three-phase flow automatically, emitting its own `enrichment` progress + heartbeat events while it works.
|
|
205
210
|
|
|
206
211
|
### Flow
|
|
207
212
|
|
|
208
213
|
```
|
|
209
|
-
Phase 1: POST /api/memory/ingest/file → text stored immediately
|
|
210
|
-
|
|
214
|
+
Phase 1: POST /api/memory/ingest/file → text stored immediately, enrichment block emitted on terminal `result`
|
|
215
|
+
Result event: { schemaVersion: 1, type: 'result', stage: 'complete', ...ingestResult, enrichment: { fileId, token, expiresAt, images: [...] } }
|
|
211
216
|
|
|
212
217
|
Phase 2: GET /api/memory/files/{fileId}/images/{imageId}?token=... → binary image
|
|
213
218
|
(SDK fetches each image, calls describeImage callback)
|
|
@@ -179,36 +179,44 @@ All from `@pyx-memory/core` except `MemoryServerError` from `@pyx-memory/client`
|
|
|
179
179
|
|
|
180
180
|
## Two-Phase PDF Enrichment
|
|
181
181
|
|
|
182
|
-
When ingesting PDFs that contain images, `
|
|
182
|
+
When ingesting PDFs that contain images, `ingestFileEvents()` runs a three-phase enrichment flow via `EnrichmentCallbacks`. The SDK handles all phases automatically and emits typed `IngestEvent`s as it goes:
|
|
183
183
|
|
|
184
|
-
1. **
|
|
185
|
-
2. **Image description** — each extracted image is fetched and described via your LLM vision callback (batched, 5 concurrent)
|
|
186
|
-
3. **Enrichment** — descriptions + entities
|
|
184
|
+
1. **Server upload** — text extracted and stored immediately; server emits `progress` events with stage `parsing` then `storing`
|
|
185
|
+
2. **Image description** — each extracted image is fetched and described via your LLM vision callback (batched, 5 concurrent); SDK emits `progress`/`heartbeat` events with stage `enrichment`
|
|
186
|
+
3. **Enrichment write** — descriptions + entities POSTed back to the server's `/enrich` endpoint; SDK emits the terminal `result` event
|
|
187
187
|
|
|
188
188
|
```typescript
|
|
189
|
-
import { MemoryClient, type EnrichmentCallbacks } from '@
|
|
189
|
+
import { MemoryClient, type EnrichmentCallbacks, type FileIngestResult } from '@pyxmate/memory';
|
|
190
190
|
|
|
191
191
|
const memory = new MemoryClient('http://localhost:7822', apiKey);
|
|
192
192
|
|
|
193
193
|
const enrichment: EnrichmentCallbacks = {
|
|
194
|
-
//
|
|
194
|
+
// Optional: describe each image using LLM vision
|
|
195
195
|
describeImage: async (imageBuffer, meta) => {
|
|
196
196
|
// imageBuffer is ArrayBuffer of the extracted PNG/JPEG
|
|
197
197
|
// meta has: imageId, pageNumber, width, height, mimeType
|
|
198
|
-
|
|
199
|
-
return description;
|
|
198
|
+
return await myVisionModel.describe(imageBuffer);
|
|
200
199
|
},
|
|
201
|
-
// Optional: extract entities from
|
|
202
|
-
|
|
200
|
+
// Optional: extract entities from text windows + image descriptions
|
|
201
|
+
extractEntitiesV2: async ({ textWindows, imageDescriptions, filename, mimeType }) => ({
|
|
203
202
|
entities: [{ name: 'Revenue', type: 'METRIC' }],
|
|
204
203
|
relationships: [{ source: 'Revenue', target: 'Q4', type: 'RELATED_TO' }],
|
|
205
204
|
}),
|
|
206
205
|
};
|
|
207
206
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
let result: FileIngestResult | null = null;
|
|
208
|
+
for await (const event of memory.ingestFileEvents(pdfFile, { enrichment })) {
|
|
209
|
+
if (event.type === 'progress') {
|
|
210
|
+
console.log(`[${event.stage}] ${event.message ?? ''}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (event.type === 'error') throw new Error(event.message ?? event.error);
|
|
214
|
+
if (event.type === 'result') {
|
|
215
|
+
const { schemaVersion: _v, type: _t, stage: _s, message: _m, ...payload } = event;
|
|
216
|
+
result = payload;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!result) throw new Error('memory ingest stream ended without a result');
|
|
212
220
|
```
|
|
213
221
|
|
|
214
|
-
Without `enrichment` callbacks, `
|
|
222
|
+
Without `enrichment` callbacks, the stream still emits `parsing` → `storing` progress events and a terminal `result` — the SDK just skips Phase 2/3.
|
|
@@ -107,13 +107,11 @@ class MemoryClient implements ExtendedMemoryInterface {
|
|
|
107
107
|
graphEdges(): Promise<{ stats: { nodeCount: number; edgeCount: number } }>;
|
|
108
108
|
graphQuery(query: { nodeId: string; depth?: number }): Promise<GraphTraversalResult>;
|
|
109
109
|
|
|
110
|
-
// File ingestion
|
|
111
|
-
//
|
|
112
|
-
// With callbacks: fetches extracted images, calls describeImage,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
enrichment?: EnrichmentCallbacks;
|
|
116
|
-
}): Promise<FileIngestResult>;
|
|
110
|
+
// File ingestion — native NDJSON streaming (v0.15.0+)
|
|
111
|
+
// Yields typed IngestEvents (progress / heartbeat / result / error).
|
|
112
|
+
// With enrichment callbacks: fetches extracted images, calls describeImage,
|
|
113
|
+
// calls extractEntitiesV2, posts /enrich, then yields the terminal `result`.
|
|
114
|
+
ingestFileEvents(file: File, options?: IngestFileOptions): AsyncIterable<IngestEvent>;
|
|
117
115
|
|
|
118
116
|
// Bi-temporal queries
|
|
119
117
|
queryAsOf(asOf: string, filters?: TemporalQueryFilters): Promise<MemoryEntry[]>;
|