@anvia/neo4j 1.1.4 → 1.1.6
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 +43 -223
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,49 +1,38 @@
|
|
|
1
1
|
# @anvia/neo4j
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build GraphRAG applications on Neo4j with typed schemas, document ingestion, and
|
|
4
|
+
retrieval that follows connected entities back to their source evidence.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
`defineNeo4jGraphSchema()` API. Registered graphs expose `graph.retrieve()`, and the shared
|
|
7
|
-
`createGraphSearchTool()` factory gives Neo4j and Memgraph the same Agent tool API.
|
|
8
|
-
|
|
9
|
-
## Installation
|
|
6
|
+
## Install
|
|
10
7
|
|
|
11
8
|
```sh
|
|
12
|
-
pnpm add @anvia/graph @anvia/
|
|
9
|
+
pnpm add @anvia/neo4j @anvia/graph @anvia/core @anvia/openai zod
|
|
13
10
|
```
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
Requires Neo4j 2026.01 or newer. `@anvia/openai` supplies the example's models.
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
Set `NEO4J_URI`, `NEO4J_USERNAME`, `NEO4J_PASSWORD`, and `OPENAI_API_KEY`.
|
|
16
17
|
|
|
17
18
|
```ts
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { defineGraphSchema, ingestGraphText } from "@anvia/graph";
|
|
20
|
+
import { Neo4jClient } from "@anvia/neo4j";
|
|
21
|
+
import { OpenAIClient } from "@anvia/openai";
|
|
21
22
|
import { z } from "zod";
|
|
22
23
|
|
|
23
|
-
const schema =
|
|
24
|
+
const schema = defineGraphSchema({
|
|
24
25
|
nodes: {
|
|
25
26
|
Product: {
|
|
26
27
|
description: "A product or service.",
|
|
27
28
|
identity: ["id"],
|
|
28
29
|
properties: z.strictObject({ id: z.string(), name: z.string() }),
|
|
29
30
|
},
|
|
30
|
-
Incident: {
|
|
31
|
-
description: "An operational incident.",
|
|
32
|
-
identity: ["id"],
|
|
33
|
-
properties: z.strictObject({ id: z.string(), title: z.string() }),
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
relationships: {
|
|
37
|
-
AFFECTS: {
|
|
38
|
-
description: "An incident affects a product.",
|
|
39
|
-
from: "Incident",
|
|
40
|
-
to: "Product",
|
|
41
|
-
properties: z.strictObject({ severity: z.enum(["low", "high"]) }),
|
|
42
|
-
},
|
|
43
31
|
},
|
|
32
|
+
relationships: {},
|
|
44
33
|
});
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
const models = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! });
|
|
35
|
+
const client = new Neo4jClient({
|
|
47
36
|
uri: process.env.NEO4J_URI!,
|
|
48
37
|
auth: {
|
|
49
38
|
username: process.env.NEO4J_USERNAME!,
|
|
@@ -56,209 +45,40 @@ const graph = client.managedKnowledgeGraph({
|
|
|
56
45
|
resources: {
|
|
57
46
|
labels: { document: "SupportDocument", chunk: "SupportChunk", entity: "SupportEntity" },
|
|
58
47
|
indexes: {
|
|
59
|
-
chunks: {
|
|
60
|
-
|
|
61
|
-
fulltext: { name: "support_chunks_text" },
|
|
62
|
-
},
|
|
63
|
-
entities: {
|
|
64
|
-
vector: { name: "support_entities_vector", dimensions: 1536, similarity: "cosine" },
|
|
65
|
-
fulltext: { name: "support_entities_text", properties: ["id", "name", "title"] },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
await graph.ensure({ indexTimeoutMs: 60_000 });
|
|
72
|
-
|
|
73
|
-
const ingestion = await ingestGraphText({
|
|
74
|
-
graph,
|
|
75
|
-
document: { id: "incident-42", text },
|
|
76
|
-
extractionModel,
|
|
77
|
-
embeddingModel,
|
|
78
|
-
chunking: {
|
|
79
|
-
strategy: "recursive",
|
|
80
|
-
maxSize: 1_000,
|
|
81
|
-
overlap: 100,
|
|
82
|
-
separators: ["\n\n", "\n", " "],
|
|
83
|
-
},
|
|
84
|
-
conflict: "error",
|
|
85
|
-
orphanEntities: "delete",
|
|
86
|
-
});
|
|
87
|
-
console.log(ingestion.write);
|
|
88
|
-
|
|
89
|
-
const searchGraph = createGraphSearchTool({
|
|
90
|
-
name: "search_support_graph",
|
|
91
|
-
description: "Search connected support incidents and products.",
|
|
92
|
-
graph,
|
|
93
|
-
model: embeddingModel,
|
|
94
|
-
search: {
|
|
95
|
-
type: "hybrid",
|
|
96
|
-
seeds: ["chunks", "entities"],
|
|
97
|
-
topK: 8,
|
|
98
|
-
candidatesPerSeed: 20,
|
|
99
|
-
rrfK: 60,
|
|
100
|
-
},
|
|
101
|
-
traversal: {
|
|
102
|
-
relationships: ["AFFECTS"],
|
|
103
|
-
direction: "both",
|
|
104
|
-
maxDepth: 2,
|
|
105
|
-
maxNodes: 40,
|
|
106
|
-
maxRelationships: 80,
|
|
107
|
-
},
|
|
108
|
-
evidence: { type: "chunks", maxChunks: 12 },
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const agent = new Agent({ id: "support", model: chatModel, tools: [searchGraph] });
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Call `ensure()` once during application startup or deployment initialization, then reuse the graph
|
|
115
|
-
handle in jobs. It is idempotent, but calling it for every ingestion job adds version, schema, and
|
|
116
|
-
index checks to the hot path. Call `validate()` for readiness checks that must never provision.
|
|
117
|
-
|
|
118
|
-
## Tenant namespaces
|
|
119
|
-
|
|
120
|
-
Reuse the same labels, indexes, and Qdrant collection across tenants without constructing resource
|
|
121
|
-
names from user input:
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
const tenant = client.tenant(userId);
|
|
125
|
-
const graph = tenant.managedKnowledgeGraph({ name: "support", schema, resources });
|
|
126
|
-
|
|
127
|
-
const qdrantTenant = qdrant.tenant(userId);
|
|
128
|
-
const vectors = qdrantTenant.vectorStore({
|
|
129
|
-
collectionName: "support_documents",
|
|
130
|
-
dimensions: 1536,
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Both adapters hash the tenant ID using the same deterministic namespace. Neo4j includes that
|
|
135
|
-
namespace in managed uniqueness constraints, indexed vector filtering, writes, retrieval,
|
|
136
|
-
traversal, evidence, deletion, and exploration. Do not mix namespaced and non-namespaced handles
|
|
137
|
-
with the same Neo4j resource definitions; `validate()` reports the incompatible constraint or
|
|
138
|
-
index so it can be migrated explicitly.
|
|
139
|
-
|
|
140
|
-
Tenant handles scope Anvia's managed lifecycle, retrieval, and exploration APIs; they are not a
|
|
141
|
-
database authorization boundary. `graph.query()` and `client.nativeDriver()` intentionally expose
|
|
142
|
-
raw Cypher access. Use separate Neo4j credentials or databases when untrusted callers require a
|
|
143
|
-
hard security boundary.
|
|
144
|
-
|
|
145
|
-
The application owns file discovery, reading, model loading, retry policy, and Agent lifecycle.
|
|
146
|
-
`ingestGraphText()` and `ingestGraphDocuments()` provide the convenient end-to-end path. Advanced
|
|
147
|
-
callers can compose `prepareGraphDocuments()`, `extractGraphFacts()`, `embedDocuments()`, and
|
|
148
|
-
`replaceDocuments()` directly when they need custom orchestration.
|
|
149
|
-
|
|
150
|
-
Graph property schemas must use `z.strictObject()` and must not coerce, default, transform,
|
|
151
|
-
preprocess, catch, trim, or otherwise overwrite values. This keeps extraction, comparison, and
|
|
152
|
-
stored Neo4j properties exact.
|
|
153
|
-
|
|
154
|
-
Re-ingestion is document-scoped. `replaceDocuments()` removes the old chunks and their provenance
|
|
155
|
-
before inserting the new representation. Facts supported by other documents remain. Use
|
|
156
|
-
`deleteDocuments({ documentIds, orphanEntities })` for explicit removal.
|
|
157
|
-
|
|
158
|
-
Both write operations return exact logical before-and-after counts for documents, chunks, entities,
|
|
159
|
-
relationships, and mentions. A resource is `created`, `updated`, `deleted`, or `unchanged` by its
|
|
160
|
-
stable public identity; the result is produced inside the same transaction as the mutation.
|
|
161
|
-
|
|
162
|
-
Retrieval always requires an explicit evidence mode. `{ type: "none" }` performs no evidence read.
|
|
163
|
-
Managed graphs can use `{ type: "chunks", maxChunks }` to hydrate exact source chunks in ranked
|
|
164
|
-
provenance order. Seeds, nodes, and relationships refer to those records through `sourceChunkIds`.
|
|
165
|
-
|
|
166
|
-
## Existing graph
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
const graph = client.knowledgeGraph({
|
|
170
|
-
schema,
|
|
171
|
-
seeds: {
|
|
172
|
-
incidents: {
|
|
173
|
-
nodeTypes: ["Incident"],
|
|
174
|
-
vectorIndex: {
|
|
175
|
-
name: "existing_incident_vectors",
|
|
176
|
-
property: "embedding",
|
|
177
|
-
dimensions: 1536,
|
|
178
|
-
similarity: "cosine",
|
|
179
|
-
},
|
|
180
|
-
fulltextIndex: {
|
|
181
|
-
name: "existing_incident_text",
|
|
182
|
-
properties: ["title"],
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
products: {
|
|
186
|
-
nodeTypes: ["Product"],
|
|
187
|
-
vectorIndex: {
|
|
188
|
-
name: "existing_product_vectors",
|
|
189
|
-
property: "embedding",
|
|
190
|
-
dimensions: 1536,
|
|
191
|
-
similarity: "cosine",
|
|
192
|
-
},
|
|
193
|
-
fulltextIndex: {
|
|
194
|
-
name: "existing_product_text",
|
|
195
|
-
properties: ["name"],
|
|
196
|
-
},
|
|
48
|
+
chunks: { vector: { name: "support_chunks", dimensions: 1536, similarity: "cosine" } },
|
|
49
|
+
entities: { vector: { name: "support_entities", dimensions: 1536, similarity: "cosine" } },
|
|
197
50
|
},
|
|
198
51
|
},
|
|
199
52
|
});
|
|
200
53
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
54
|
+
try {
|
|
55
|
+
await graph.ensure({ indexTimeoutMs: 60_000 });
|
|
56
|
+
const result = await ingestGraphText({
|
|
57
|
+
graph,
|
|
58
|
+
document: { id: "products", text: "The product with ID anvia is named Anvia." },
|
|
59
|
+
extractionModel: models.completionModel({ modelId: "gpt-5.6", api: "responses" }),
|
|
60
|
+
embeddingModel: models.embeddingModel({ modelId: "text-embedding-3-small" }),
|
|
61
|
+
conflict: "error",
|
|
62
|
+
orphanEntities: "delete",
|
|
63
|
+
});
|
|
64
|
+
console.log(result.write);
|
|
65
|
+
} finally {
|
|
66
|
+
await client.close();
|
|
67
|
+
}
|
|
215
68
|
```
|
|
216
69
|
|
|
217
|
-
|
|
218
|
-
and relationships. This can drive article-to-entity UI links without a separate identity mapping
|
|
219
|
-
table; Neo4j element IDs remain opaque expansion cursors.
|
|
220
|
-
|
|
221
|
-
## Production ingestion workflow
|
|
222
|
-
|
|
223
|
-
For a queue worker (including BullMQ), use stable document IDs, attach a monotonic revision, and
|
|
224
|
-
call `ingestGraphTextToStores({ graph, vectorStore, ... })`. Persist its receipt as the job result.
|
|
225
|
-
Retry extraction/provider failures as a whole. If `GraphIngestionStageError.stage === "vector"`,
|
|
226
|
-
the graph transaction already committed; re-run the idempotent job with `conflict: "overwrite"`.
|
|
227
|
-
Refresh by replacing the same document ID;
|
|
228
|
-
delete from both stores and record each completed stage. Pass the queue cancellation signal through
|
|
229
|
-
`abortSignal`, create tenant-scoped handles before processing user-owned jobs, and request explorer
|
|
230
|
-
provenance for source attribution.
|
|
70
|
+
## What you can build
|
|
231
71
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
72
|
+
- Managed graphs with explicit provisioning and document-scoped replacement.
|
|
73
|
+
- Vector and hybrid retrieval with bounded traversal and source chunk evidence.
|
|
74
|
+
- Agent search tools through `createGraphSearchTool()` from `@anvia/graph`.
|
|
75
|
+
- Bounded exploration and read-only registration of existing graphs.
|
|
235
76
|
|
|
236
|
-
|
|
237
|
-
|
|
77
|
+
Tenant handles scope managed graph operations in shared resources. They are not a database
|
|
78
|
+
authorization boundary. Existing graph registrations are read-only.
|
|
238
79
|
|
|
239
|
-
|
|
240
|
-
tool registrations accept only `evidence: { type: "none" }`.
|
|
80
|
+
## Learn more
|
|
241
81
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Package operations use explicit transactions and do not invoke Neo4j's managed retry APIs. Abort
|
|
247
|
-
signals roll back the active transaction. Applications decide whether and how to retry an entire
|
|
248
|
-
operation.
|
|
249
|
-
|
|
250
|
-
`Neo4jClient` supports `await using` and closes a driver that it creates from `uri` and `auth`.
|
|
251
|
-
When a caller supplies `driver`, that driver remains caller-owned; closing or disposing the client
|
|
252
|
-
only closes the Anvia client handle.
|
|
253
|
-
|
|
254
|
-
The public property boundary is intentionally JSON-safe. Unsafe Neo4j integers, temporal and
|
|
255
|
-
spatial objects, nodes, relationships, paths, maps, and heterogeneous property arrays are rejected
|
|
256
|
-
instead of being coerced.
|
|
257
|
-
|
|
258
|
-
## Development
|
|
259
|
-
|
|
260
|
-
```sh
|
|
261
|
-
pnpm --filter @anvia/neo4j typecheck
|
|
262
|
-
pnpm --filter @anvia/neo4j test
|
|
263
|
-
pnpm --filter @anvia/neo4j build
|
|
264
|
-
```
|
|
82
|
+
- [Usage guide](https://github.com/anvia-hq/anvia/blob/main/docs/packages/graph-neo4j.md)
|
|
83
|
+
- [Anvia overview](https://github.com/anvia-hq/anvia/blob/main/README.md)
|
|
84
|
+
- [Contributing](https://github.com/anvia-hq/anvia/blob/main/CONTRIBUTING.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anvia/neo4j",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Schema-first Neo4j GraphRAG integration for Anvia.",
|
|
5
5
|
"author": "anvia",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,19 +26,19 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"neo4j-driver": "^6.2.0",
|
|
29
|
-
"@anvia/graph": "1.1.
|
|
29
|
+
"@anvia/graph": "1.1.6"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^24.9.1",
|
|
33
33
|
"tsup": "^8.5.0",
|
|
34
34
|
"typescript": "^5.9.3",
|
|
35
|
-
"vitest": "^4.
|
|
36
|
-
"zod": "^4.6.
|
|
37
|
-
"@anvia/core": "1.
|
|
35
|
+
"vitest": "^4.1.11",
|
|
36
|
+
"zod": "^4.6.7",
|
|
37
|
+
"@anvia/core": "1.17.2"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"zod": "^4.6.
|
|
41
|
-
"@anvia/core": "^1.
|
|
40
|
+
"zod": "^4.6.7",
|
|
41
|
+
"@anvia/core": "^1.17.2"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm --dts --sourcemap --clean",
|