@anvia/neo4j 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +43 -223
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,49 +1,38 @@
1
1
  # @anvia/neo4j
2
2
 
3
- Schema-first local GraphRAG for Neo4j 2026.01 and newer.
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
- Provider-neutral schemas from `@anvia/graph` are accepted alongside the compatibility
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/neo4j @anvia/core
9
+ pnpm add @anvia/neo4j @anvia/graph @anvia/core @anvia/openai zod
13
10
  ```
14
11
 
15
- ## Managed knowledge graph
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 { Agent } from "@anvia/core/agent";
19
- import { createGraphSearchTool, ingestGraphText } from "@anvia/graph";
20
- import { Neo4jClient, defineNeo4jGraphSchema } from "@anvia/neo4j";
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 = defineNeo4jGraphSchema({
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
- await using client = new Neo4jClient({
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
- vector: { name: "support_chunks_vector", dimensions: 1536, similarity: "cosine" },
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
- await graph.validate();
202
- ```
203
-
204
- ## Graph explorer
205
-
206
- Managed and existing graph registrations implement the shared `GraphExplorer` contract:
207
-
208
- ```ts
209
- const overview = await graph.explore({ mode: "overview", maxNodes: 100 });
210
- const neighborhood = await graph.explore({
211
- mode: "expand",
212
- nodeIds: [overview.nodes[0]!.id],
213
- maxDepth: 1,
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
- Pass `includeProvenance: true` to expose supported source document and chunk IDs on explorer nodes
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
- The explorer returns Neo4j element IDs as opaque strings, limits every query, and omits embeddings
233
- and other `__anvia_*` properties. Use stable graph identity properties for application logic; use the
234
- opaque IDs only to expand the current view.
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
- Existing registrations are read-only and expose no provisioning or write methods. Use
237
- `client.nativeDriver()` for application-owned Cypher.
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
- Existing graph registrations do not own Anvia document and chunk resources, so their retrieval and
240
- tool registrations accept only `evidence: { type: "none" }`.
80
+ ## Learn more
241
81
 
242
- Each existing seed describes exactly one indexed node type and the complete expected vector and
243
- full-text index definitions. Register additional node types as separate seeds and fuse them with
244
- hybrid retrieval.
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.4",
3
+ "version": "1.1.5",
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.4"
29
+ "@anvia/graph": "1.1.5"
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.0.8",
35
+ "vitest": "^4.1.11",
36
36
  "zod": "^4.6.5",
37
- "@anvia/core": "1.3.1"
37
+ "@anvia/core": "1.6.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "zod": "^4.6.5",
41
- "@anvia/core": "^1.3.1"
41
+ "@anvia/core": "^1.6.1"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm --dts --sourcemap --clean",