@falkordb/text-to-cypher 0.1.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/CHANGELOG.md +48 -0
- package/INTEGRATION.md +373 -0
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/examples/README.md +50 -0
- package/examples/basic-usage.js +94 -0
- package/examples/typescript-usage.ts +52 -0
- package/index.d.ts +174 -0
- package/index.js +315 -0
- package/package.json +87 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-12-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release of text-to-cypher-node
|
|
12
|
+
- NAPI-RS bindings for the text-to-cypher Rust library
|
|
13
|
+
- `TextToCypher` class with the following methods:
|
|
14
|
+
- `textToCypher(graphName, question)` - Convert natural language to Cypher and execute
|
|
15
|
+
- `textToCypherWithMessages(graphName, messages)` - Support for conversation history
|
|
16
|
+
- `cypherOnly(graphName, question)` - Generate Cypher without execution
|
|
17
|
+
- `discoverSchema(graphName)` - Discover and return graph schema
|
|
18
|
+
- TypeScript definitions for all public APIs
|
|
19
|
+
- Comprehensive documentation:
|
|
20
|
+
- README.md with usage examples
|
|
21
|
+
- INTEGRATION.md for FalkorDB Browser integration
|
|
22
|
+
- CONTRIBUTING.md for developers
|
|
23
|
+
- Examples:
|
|
24
|
+
- basic-usage.js - JavaScript example
|
|
25
|
+
- typescript-usage.ts - TypeScript example
|
|
26
|
+
- Test suite using Vitest
|
|
27
|
+
- GitHub Actions CI/CD:
|
|
28
|
+
- Build workflow for multiple platforms (Linux, macOS, Windows)
|
|
29
|
+
- Test workflow
|
|
30
|
+
- Release workflow with automatic npm publishing
|
|
31
|
+
- Support for multiple AI models:
|
|
32
|
+
- OpenAI (GPT-4, GPT-4 Turbo, GPT-4o Mini, etc.)
|
|
33
|
+
- Anthropic (Claude 3)
|
|
34
|
+
- Google (Gemini)
|
|
35
|
+
- Pre-built binaries for:
|
|
36
|
+
- Linux x64 (glibc and musl)
|
|
37
|
+
- Linux ARM64 (glibc and musl)
|
|
38
|
+
- macOS x64 (Intel)
|
|
39
|
+
- macOS ARM64 (Apple Silicon)
|
|
40
|
+
- Windows x64
|
|
41
|
+
|
|
42
|
+
### Technical Details
|
|
43
|
+
- Built with NAPI-RS for high-performance native Node.js bindings
|
|
44
|
+
- Async/await support throughout the API
|
|
45
|
+
- Proper error handling and propagation from Rust to JavaScript
|
|
46
|
+
- Zero runtime dependencies
|
|
47
|
+
|
|
48
|
+
[0.1.0]: https://github.com/FalkorDB/text-to-cypher-node/releases/tag/v0.1.0
|
package/INTEGRATION.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# Integration Guide for FalkorDB Browser
|
|
2
|
+
|
|
3
|
+
This guide explains how to integrate `@falkordb/text-to-cypher-node` into the [FalkorDB Browser](https://github.com/FalkorDB/falkordb-browser).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The FalkorDB Browser can use text-to-cypher-node to provide a natural language interface for querying graphs. Users can ask questions in plain English instead of writing Cypher queries.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
In your FalkorDB Browser project:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @falkordb/text-to-cypher-node
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Backend Integration (Node.js/Express)
|
|
18
|
+
|
|
19
|
+
### 1. Create a Text-to-Cypher Service
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// services/textToCypherService.ts
|
|
23
|
+
import { TextToCypher, TextToCypherResponse } from '@falkordb/text-to-cypher-node';
|
|
24
|
+
|
|
25
|
+
export class TextToCypherService {
|
|
26
|
+
private client: TextToCypher;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
model: string = process.env.AI_MODEL || 'gpt-4o-mini',
|
|
30
|
+
apiKey: string = process.env.OPENAI_API_KEY || '',
|
|
31
|
+
falkordbConnection: string = process.env.FALKORDB_CONNECTION || 'falkor://localhost:6379'
|
|
32
|
+
) {
|
|
33
|
+
this.client = new TextToCypher({
|
|
34
|
+
model,
|
|
35
|
+
apiKey,
|
|
36
|
+
falkordbConnection
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async convertQuery(graphName: string, question: string): Promise<TextToCypherResponse> {
|
|
41
|
+
return await this.client.textToCypher(graphName, question);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async generateCypherOnly(graphName: string, question: string): Promise<TextToCypherResponse> {
|
|
45
|
+
return await this.client.cypherOnly(graphName, question);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getSchema(graphName: string): Promise<string> {
|
|
49
|
+
return await this.client.discoverSchema(graphName);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Create API Endpoints
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// routes/textToCypher.ts
|
|
58
|
+
import { Router, Request, Response } from 'express';
|
|
59
|
+
import { TextToCypherService } from '../services/textToCypherService';
|
|
60
|
+
|
|
61
|
+
const router = Router();
|
|
62
|
+
const textToCypherService = new TextToCypherService();
|
|
63
|
+
|
|
64
|
+
// Convert natural language to Cypher and execute
|
|
65
|
+
router.post('/api/text-to-cypher', async (req: Request, res: Response) => {
|
|
66
|
+
try {
|
|
67
|
+
const { graphName, question } = req.body;
|
|
68
|
+
|
|
69
|
+
if (!graphName || !question) {
|
|
70
|
+
return res.status(400).json({
|
|
71
|
+
error: 'Missing required fields: graphName and question'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const response = await textToCypherService.convertQuery(graphName, question);
|
|
76
|
+
|
|
77
|
+
if (response.status === 'error') {
|
|
78
|
+
return res.status(500).json(response);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
res.json(response);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
res.status(500).json({
|
|
84
|
+
error: 'Internal server error',
|
|
85
|
+
message: error instanceof Error ? error.message : 'Unknown error'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Generate Cypher only (for preview)
|
|
91
|
+
router.post('/api/generate-cypher', async (req: Request, res: Response) => {
|
|
92
|
+
try {
|
|
93
|
+
const { graphName, question } = req.body;
|
|
94
|
+
|
|
95
|
+
if (!graphName || !question) {
|
|
96
|
+
return res.status(400).json({
|
|
97
|
+
error: 'Missing required fields: graphName and question'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const response = await textToCypherService.generateCypherOnly(graphName, question);
|
|
102
|
+
|
|
103
|
+
if (response.status === 'error') {
|
|
104
|
+
return res.status(500).json(response);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
res.json(response);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
error: 'Internal server error',
|
|
111
|
+
message: error instanceof Error ? error.message : 'Unknown error'
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Get graph schema
|
|
117
|
+
router.get('/api/schema/:graphName', async (req: Request, res: Response) => {
|
|
118
|
+
try {
|
|
119
|
+
const { graphName } = req.params;
|
|
120
|
+
const schema = await textToCypherService.getSchema(graphName);
|
|
121
|
+
res.json({ schema: JSON.parse(schema) });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
res.status(500).json({
|
|
124
|
+
error: 'Internal server error',
|
|
125
|
+
message: error instanceof Error ? error.message : 'Unknown error'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export default router;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 3. Register Routes in Your App
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// app.ts
|
|
137
|
+
import express from 'express';
|
|
138
|
+
import textToCypherRoutes from './routes/textToCypher';
|
|
139
|
+
|
|
140
|
+
const app = express();
|
|
141
|
+
|
|
142
|
+
app.use(express.json());
|
|
143
|
+
app.use(textToCypherRoutes);
|
|
144
|
+
|
|
145
|
+
// ... other routes and middleware
|
|
146
|
+
|
|
147
|
+
export default app;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Frontend Integration (React)
|
|
151
|
+
|
|
152
|
+
### 1. Create a Text-to-Cypher Hook
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// hooks/useTextToCypher.ts
|
|
156
|
+
import { useState } from 'react';
|
|
157
|
+
|
|
158
|
+
interface TextToCypherResponse {
|
|
159
|
+
status: string;
|
|
160
|
+
schema?: string;
|
|
161
|
+
cypherQuery?: string;
|
|
162
|
+
cypherResult?: string;
|
|
163
|
+
answer?: string;
|
|
164
|
+
error?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const useTextToCypher = () => {
|
|
168
|
+
const [loading, setLoading] = useState(false);
|
|
169
|
+
const [error, setError] = useState<string | null>(null);
|
|
170
|
+
|
|
171
|
+
const convertQuery = async (
|
|
172
|
+
graphName: string,
|
|
173
|
+
question: string
|
|
174
|
+
): Promise<TextToCypherResponse | null> => {
|
|
175
|
+
setLoading(true);
|
|
176
|
+
setError(null);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch('/api/text-to-cypher', {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: {
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({ graphName, question }),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const data = await response.json();
|
|
188
|
+
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
throw new Error(data.error || 'Failed to convert query');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return data;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
196
|
+
setError(errorMessage);
|
|
197
|
+
return null;
|
|
198
|
+
} finally {
|
|
199
|
+
setLoading(false);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const generateCypher = async (
|
|
204
|
+
graphName: string,
|
|
205
|
+
question: string
|
|
206
|
+
): Promise<TextToCypherResponse | null> => {
|
|
207
|
+
setLoading(true);
|
|
208
|
+
setError(null);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetch('/api/generate-cypher', {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify({ graphName, question }),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const data = await response.json();
|
|
220
|
+
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
throw new Error(data.error || 'Failed to generate query');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return data;
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
228
|
+
setError(errorMessage);
|
|
229
|
+
return null;
|
|
230
|
+
} finally {
|
|
231
|
+
setLoading(false);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return { convertQuery, generateCypher, loading, error };
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 2. Create a Natural Language Query Component
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// components/NaturalLanguageQuery.tsx
|
|
243
|
+
import React, { useState } from 'react';
|
|
244
|
+
import { useTextToCypher } from '../hooks/useTextToCypher';
|
|
245
|
+
|
|
246
|
+
interface Props {
|
|
247
|
+
graphName: string;
|
|
248
|
+
onQueryGenerated?: (query: string) => void;
|
|
249
|
+
onResultReceived?: (result: string, answer: string) => void;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const NaturalLanguageQuery: React.FC<Props> = ({
|
|
253
|
+
graphName,
|
|
254
|
+
onQueryGenerated,
|
|
255
|
+
onResultReceived,
|
|
256
|
+
}) => {
|
|
257
|
+
const [question, setQuestion] = useState('');
|
|
258
|
+
const { convertQuery, generateCypher, loading, error } = useTextToCypher();
|
|
259
|
+
|
|
260
|
+
const handleConvert = async () => {
|
|
261
|
+
const response = await convertQuery(graphName, question);
|
|
262
|
+
if (response && response.status === 'success') {
|
|
263
|
+
if (response.cypherQuery) {
|
|
264
|
+
onQueryGenerated?.(response.cypherQuery);
|
|
265
|
+
}
|
|
266
|
+
if (response.cypherResult && response.answer) {
|
|
267
|
+
onResultReceived?.(response.cypherResult, response.answer);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const handleGenerateOnly = async () => {
|
|
273
|
+
const response = await generateCypher(graphName, question);
|
|
274
|
+
if (response && response.cypherQuery) {
|
|
275
|
+
onQueryGenerated?.(response.cypherQuery);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className="natural-language-query">
|
|
281
|
+
<h3>Ask a Question</h3>
|
|
282
|
+
<textarea
|
|
283
|
+
value={question}
|
|
284
|
+
onChange={(e) => setQuestion(e.target.value)}
|
|
285
|
+
placeholder="e.g., Find all actors who appeared in movies after 2020"
|
|
286
|
+
disabled={loading}
|
|
287
|
+
/>
|
|
288
|
+
<div className="buttons">
|
|
289
|
+
<button onClick={handleConvert} disabled={loading || !question}>
|
|
290
|
+
{loading ? 'Processing...' : 'Execute Query'}
|
|
291
|
+
</button>
|
|
292
|
+
<button onClick={handleGenerateOnly} disabled={loading || !question}>
|
|
293
|
+
Generate Query Only
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
{error && <div className="error">{error}</div>}
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Environment Variables
|
|
303
|
+
|
|
304
|
+
Create a `.env` file in your project:
|
|
305
|
+
|
|
306
|
+
```env
|
|
307
|
+
# AI Model Configuration
|
|
308
|
+
AI_MODEL=gpt-4o-mini
|
|
309
|
+
OPENAI_API_KEY=your-openai-api-key-here
|
|
310
|
+
|
|
311
|
+
# FalkorDB Connection
|
|
312
|
+
FALKORDB_CONNECTION=falkor://localhost:6379
|
|
313
|
+
|
|
314
|
+
# Optional: for other AI providers
|
|
315
|
+
# ANTHROPIC_API_KEY=your-anthropic-key
|
|
316
|
+
# GOOGLE_API_KEY=your-google-key
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Usage Examples
|
|
320
|
+
|
|
321
|
+
### Example 1: Simple Query
|
|
322
|
+
|
|
323
|
+
User asks: "Show me all actors"
|
|
324
|
+
|
|
325
|
+
Response:
|
|
326
|
+
- Generated Query: `MATCH (a:Actor) RETURN a.name`
|
|
327
|
+
- Result: List of actor names
|
|
328
|
+
- Answer: "Here are all the actors in the database: Tom Hanks, Meryl Streep, ..."
|
|
329
|
+
|
|
330
|
+
### Example 2: Complex Query
|
|
331
|
+
|
|
332
|
+
User asks: "Find actors who worked with Tom Hanks in movies released after 2000"
|
|
333
|
+
|
|
334
|
+
Response:
|
|
335
|
+
- Generated Query: `MATCH (a:Actor)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(coactor:Actor {name: 'Tom Hanks'}) WHERE m.year > 2000 RETURN DISTINCT a.name`
|
|
336
|
+
- Result: List of co-actors
|
|
337
|
+
- Answer: "Tom Hanks worked with the following actors in movies after 2000: ..."
|
|
338
|
+
|
|
339
|
+
## Best Practices
|
|
340
|
+
|
|
341
|
+
1. **Rate Limiting**: Implement rate limiting on the API endpoints to prevent abuse
|
|
342
|
+
2. **Caching**: Cache schema discovery results to reduce database queries
|
|
343
|
+
3. **Error Handling**: Provide helpful error messages to users
|
|
344
|
+
4. **User Feedback**: Show the generated Cypher query to users for transparency
|
|
345
|
+
5. **Query Validation**: Always validate and sanitize queries before execution
|
|
346
|
+
|
|
347
|
+
## Security Considerations
|
|
348
|
+
|
|
349
|
+
1. Never expose your AI API keys in frontend code
|
|
350
|
+
2. Implement proper authentication for the API endpoints
|
|
351
|
+
3. Use read-only connections when possible
|
|
352
|
+
4. Consider implementing a query whitelist for production environments
|
|
353
|
+
5. Monitor usage to detect potential abuse
|
|
354
|
+
|
|
355
|
+
## Troubleshooting
|
|
356
|
+
|
|
357
|
+
### Issue: "Cannot connect to FalkorDB"
|
|
358
|
+
|
|
359
|
+
Solution: Ensure FalkorDB is running and the connection string is correct.
|
|
360
|
+
|
|
361
|
+
### Issue: "Invalid API key"
|
|
362
|
+
|
|
363
|
+
Solution: Check that your AI provider API key is set correctly in environment variables.
|
|
364
|
+
|
|
365
|
+
### Issue: "Query generation failed"
|
|
366
|
+
|
|
367
|
+
Solution: The AI model might need more context. Try rephrasing the question or providing more specific details.
|
|
368
|
+
|
|
369
|
+
## Support
|
|
370
|
+
|
|
371
|
+
For issues or questions:
|
|
372
|
+
- [text-to-cypher-node GitHub Issues](https://github.com/FalkorDB/text-to-cypher-node/issues)
|
|
373
|
+
- [FalkorDB Discord](https://discord.gg/falkordb)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FalkorDB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|