flowquery 1.0.25 → 1.0.26
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/.github/workflows/release.yml +1 -0
- package/.husky/pre-commit +3 -2
- package/dist/flowquery.min.js +1 -1
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +1 -0
- package/dist/parsing/parser.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/pyproject.toml +1 -1
- package/flowquery-py/src/parsing/parser.py +1 -0
- package/flowquery-py/tests/compute/test_runner.py +26 -0
- package/flowquery-py/tests/parsing/test_parser.py +18 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/jest.config.js +6 -9
- package/misc/apps/RAG/data/chats.json +302 -0
- package/misc/apps/RAG/data/emails.json +182 -0
- package/misc/apps/RAG/data/events.json +226 -0
- package/misc/apps/RAG/data/files.json +172 -0
- package/misc/apps/RAG/data/users.json +158 -0
- package/misc/apps/RAG/jest.config.js +21 -0
- package/misc/apps/RAG/package.json +9 -2
- package/misc/apps/RAG/src/App.tsx +5 -5
- package/misc/apps/RAG/src/components/ChatContainer.tsx +53 -124
- package/misc/apps/RAG/src/components/FlowQueryAgent.ts +151 -157
- package/misc/apps/RAG/src/components/index.ts +1 -1
- package/misc/apps/RAG/src/graph/index.ts +19 -0
- package/misc/apps/RAG/src/graph/initializeGraph.ts +254 -0
- package/misc/apps/RAG/src/index.tsx +25 -13
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +146 -231
- package/misc/apps/RAG/src/prompts/index.ts +4 -4
- package/misc/apps/RAG/src/tests/graph.test.ts +35 -0
- package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +20 -21
- package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +35 -30
- package/misc/apps/RAG/src/utils/Llm.ts +248 -0
- package/misc/apps/RAG/src/utils/index.ts +7 -4
- package/misc/apps/RAG/tsconfig.json +4 -3
- package/misc/apps/RAG/webpack.config.js +40 -40
- package/package.json +1 -1
- package/src/parsing/parser.ts +1 -0
- package/tests/compute/runner.test.ts +1 -1
- package/tests/parsing/parser.test.ts +16 -0
- package/misc/apps/RAG/src/plugins/README.md +0 -139
- package/misc/apps/RAG/src/plugins/index.ts +0 -72
- package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +0 -70
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +0 -65
- package/misc/apps/RAG/src/plugins/loaders/Form.ts +0 -594
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +0 -450
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +0 -101
- package/misc/apps/RAG/src/plugins/loaders/Table.ts +0 -274
- package/misc/apps/RAG/src/plugins/loaders/Weather.ts +0 -138
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlowQuery Executor Utility
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Executes FlowQuery statements and handles errors gracefully.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import { FlowQuery } from 'flowquery';
|
|
6
|
+
import { FlowQuery } from "flowquery";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Result of executing a FlowQuery statement.
|
|
@@ -24,7 +23,7 @@ export interface FlowQueryExecutionResult {
|
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* FlowQuery Executor class for executing FlowQuery statements.
|
|
27
|
-
*
|
|
26
|
+
*
|
|
28
27
|
* @example
|
|
29
28
|
* ```typescript
|
|
30
29
|
* const executor = new FlowQueryExecutor();
|
|
@@ -49,52 +48,52 @@ export class FlowQueryExecutor {
|
|
|
49
48
|
|
|
50
49
|
/**
|
|
51
50
|
* Execute a FlowQuery statement and return the results.
|
|
52
|
-
*
|
|
51
|
+
*
|
|
53
52
|
* @param query - The FlowQuery statement to execute
|
|
54
53
|
* @returns The execution result including success status, results or error
|
|
55
54
|
*/
|
|
56
55
|
async execute(query: string): Promise<FlowQueryExecutionResult> {
|
|
57
56
|
const startTime = performance.now();
|
|
58
|
-
|
|
57
|
+
|
|
59
58
|
try {
|
|
60
59
|
// Validate the query is not empty
|
|
61
|
-
if (!query || query.trim() ===
|
|
60
|
+
if (!query || query.trim() === "") {
|
|
62
61
|
return {
|
|
63
62
|
success: false,
|
|
64
63
|
query,
|
|
65
|
-
error:
|
|
66
|
-
executionTime: performance.now() - startTime
|
|
64
|
+
error: "Query cannot be empty",
|
|
65
|
+
executionTime: performance.now() - startTime,
|
|
67
66
|
};
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
// Create a runner and execute the query
|
|
71
70
|
const runner = new FlowQuery(query);
|
|
72
71
|
await runner.run();
|
|
73
|
-
|
|
72
|
+
|
|
74
73
|
// Get the results
|
|
75
74
|
const results = runner.results;
|
|
76
|
-
|
|
75
|
+
|
|
77
76
|
return {
|
|
78
77
|
success: true,
|
|
79
78
|
query,
|
|
80
79
|
results: Array.isArray(results) ? results : [results],
|
|
81
|
-
executionTime: performance.now() - startTime
|
|
80
|
+
executionTime: performance.now() - startTime,
|
|
82
81
|
};
|
|
83
82
|
} catch (error) {
|
|
84
83
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
85
|
-
|
|
84
|
+
|
|
86
85
|
return {
|
|
87
86
|
success: false,
|
|
88
87
|
query,
|
|
89
88
|
error: errorMessage,
|
|
90
|
-
executionTime: performance.now() - startTime
|
|
89
|
+
executionTime: performance.now() - startTime,
|
|
91
90
|
};
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/**
|
|
96
95
|
* Format execution results for display or LLM consumption.
|
|
97
|
-
*
|
|
96
|
+
*
|
|
98
97
|
* @param result - The execution result to format
|
|
99
98
|
* @param maxItems - Maximum number of items to include (uses default if not specified)
|
|
100
99
|
* @returns A formatted string representation of the results
|
|
@@ -109,21 +108,21 @@ export class FlowQueryExecutor {
|
|
|
109
108
|
const results = result.results || [];
|
|
110
109
|
const totalCount = results.length;
|
|
111
110
|
const displayResults = results.slice(0, limit);
|
|
112
|
-
|
|
111
|
+
|
|
113
112
|
let output = `Execution successful (${result.executionTime.toFixed(2)}ms)\n`;
|
|
114
113
|
output += `Total results: ${totalCount}\n\n`;
|
|
115
|
-
|
|
114
|
+
|
|
116
115
|
if (totalCount === 0) {
|
|
117
|
-
output +=
|
|
116
|
+
output += "No results returned.";
|
|
118
117
|
} else {
|
|
119
|
-
output +=
|
|
118
|
+
output += "Results:\n";
|
|
120
119
|
output += JSON.stringify(displayResults, null, 2);
|
|
121
|
-
|
|
120
|
+
|
|
122
121
|
if (totalCount > limit) {
|
|
123
122
|
output += `\n\n... and ${totalCount - limit} more results`;
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
|
-
|
|
125
|
+
|
|
127
126
|
return output;
|
|
128
127
|
}
|
|
129
128
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlowQuery Extraction Utility
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Extracts FlowQuery statements from LLM responses.
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -22,10 +22,10 @@ export interface FlowQueryExtraction {
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* FlowQuery Extractor class for extracting FlowQuery statements from LLM responses.
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* Looks for code blocks with flowquery, cypher, or sql language tags,
|
|
27
27
|
* or generic code blocks that appear to contain FlowQuery syntax.
|
|
28
|
-
*
|
|
28
|
+
*
|
|
29
29
|
* @example
|
|
30
30
|
* ```typescript
|
|
31
31
|
* const extractor = new FlowQueryExtractor();
|
|
@@ -34,7 +34,7 @@ export interface FlowQueryExtraction {
|
|
|
34
34
|
* LOAD JSON FROM somePlugin(5) AS item
|
|
35
35
|
* RETURN item.text
|
|
36
36
|
* \`\`\``;
|
|
37
|
-
*
|
|
37
|
+
*
|
|
38
38
|
* const extraction = extractor.extract(response);
|
|
39
39
|
* console.log(extraction.query); // "LOAD JSON FROM somePlugin(5) AS item\nRETURN item.text"
|
|
40
40
|
* ```
|
|
@@ -44,47 +44,47 @@ export class FlowQueryExtractor {
|
|
|
44
44
|
private readonly codeBlockPatterns: RegExp[] = [
|
|
45
45
|
/```(?:flowquery|cypher|fql)\s*\n([\s\S]*?)```/i,
|
|
46
46
|
/```(?:sql)\s*\n([\s\S]*?)```/i,
|
|
47
|
-
/```\s*\n([\s\S]*?)```/,
|
|
47
|
+
/```\s*\n([\s\S]*?)```/, // Generic code block
|
|
48
48
|
];
|
|
49
49
|
|
|
50
50
|
/** Keywords that indicate a FlowQuery statement */
|
|
51
51
|
private readonly flowQueryKeywords: RegExp[] = [
|
|
52
|
+
/\bMATCH\b/i,
|
|
53
|
+
/\bCREATE\b/i,
|
|
52
54
|
/\bWITH\b/i,
|
|
53
55
|
/\bLOAD\s+JSON\s+FROM\b/i,
|
|
54
56
|
/\bUNWIND\b/i,
|
|
55
57
|
/\bRETURN\b/i,
|
|
56
58
|
/\bWHERE\b/i,
|
|
57
|
-
/\bORDER\s+BY\b/i,
|
|
58
|
-
/\bLIMIT\b/i,
|
|
59
59
|
];
|
|
60
60
|
|
|
61
61
|
/** Keywords that can start a FlowQuery statement */
|
|
62
|
-
private readonly startKeywords = /^(WITH|LOAD\s+JSON\s+FROM|UNWIND)\b/i;
|
|
62
|
+
private readonly startKeywords = /^(MATCH|CREATE|WITH|LOAD\s+JSON\s+FROM|UNWIND|CALL)\b/i;
|
|
63
63
|
|
|
64
64
|
/** Keywords that can continue a FlowQuery statement */
|
|
65
|
-
private readonly continueKeywords =
|
|
65
|
+
private readonly continueKeywords =
|
|
66
|
+
/^(MATCH|CREATE|WITH|LOAD|UNWIND|WHERE|RETURN|CALL|YIELD|HEADERS|POST|AS)\b/i;
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* Extract a FlowQuery statement from an LLM response.
|
|
69
|
-
*
|
|
70
|
+
*
|
|
70
71
|
* @param llmResponse - The full text response from the LLM
|
|
71
72
|
* @returns The extraction result
|
|
72
73
|
*/
|
|
73
74
|
public extract(llmResponse: string): FlowQueryExtraction {
|
|
74
|
-
if (!llmResponse || llmResponse.trim() ===
|
|
75
|
+
if (!llmResponse || llmResponse.trim() === "") {
|
|
75
76
|
return { query: null, found: false };
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// Check for explicit "NO_QUERY_NEEDED" marker
|
|
79
|
-
if (llmResponse.includes(
|
|
80
|
-
llmResponse.includes('NO_QUERY_NEEDED')) {
|
|
80
|
+
if (llmResponse.includes("[NO_QUERY_NEEDED]") || llmResponse.includes("NO_QUERY_NEEDED")) {
|
|
81
81
|
// Extract the direct response after the marker
|
|
82
82
|
const directMatch = llmResponse.match(/\[NO_QUERY_NEEDED\]\s*([\s\S]*)/i);
|
|
83
83
|
return {
|
|
84
84
|
query: null,
|
|
85
85
|
found: false,
|
|
86
86
|
noQueryNeeded: true,
|
|
87
|
-
directResponse: directMatch ? directMatch[1].trim() : llmResponse
|
|
87
|
+
directResponse: directMatch ? directMatch[1].trim() : llmResponse,
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -93,16 +93,18 @@ export class FlowQueryExtractor {
|
|
|
93
93
|
const match = llmResponse.match(pattern);
|
|
94
94
|
if (match && match[1]) {
|
|
95
95
|
const query = match[1].trim();
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
// Verify it looks like a FlowQuery statement
|
|
98
98
|
if (this.isLikelyFlowQuery(query)) {
|
|
99
99
|
// Extract explanation text before the code block
|
|
100
|
-
const beforeMatch = llmResponse
|
|
101
|
-
|
|
100
|
+
const beforeMatch = llmResponse
|
|
101
|
+
.substring(0, llmResponse.indexOf(match[0]))
|
|
102
|
+
.trim();
|
|
103
|
+
|
|
102
104
|
return {
|
|
103
105
|
query,
|
|
104
106
|
found: true,
|
|
105
|
-
explanation: beforeMatch || undefined
|
|
107
|
+
explanation: beforeMatch || undefined,
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -114,7 +116,7 @@ export class FlowQueryExtractor {
|
|
|
114
116
|
if (inlineQuery) {
|
|
115
117
|
return {
|
|
116
118
|
query: inlineQuery,
|
|
117
|
-
found: true
|
|
119
|
+
found: true,
|
|
118
120
|
};
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -124,7 +126,7 @@ export class FlowQueryExtractor {
|
|
|
124
126
|
/**
|
|
125
127
|
* Extract multiple FlowQuery statements from an LLM response.
|
|
126
128
|
* Useful when the LLM provides alternative queries.
|
|
127
|
-
*
|
|
129
|
+
*
|
|
128
130
|
* @param llmResponse - The full text response from the LLM
|
|
129
131
|
* @returns Array of extracted queries
|
|
130
132
|
*/
|
|
@@ -133,7 +135,7 @@ export class FlowQueryExtractor {
|
|
|
133
135
|
|
|
134
136
|
const queries: string[] = [];
|
|
135
137
|
const codeBlockPattern = /```(?:flowquery|cypher|fql|sql)?\s*\n([\s\S]*?)```/gi;
|
|
136
|
-
|
|
138
|
+
|
|
137
139
|
let match;
|
|
138
140
|
while ((match = codeBlockPattern.exec(llmResponse)) !== null) {
|
|
139
141
|
if (match[1]) {
|
|
@@ -152,31 +154,34 @@ export class FlowQueryExtractor {
|
|
|
152
154
|
*/
|
|
153
155
|
private isLikelyFlowQuery(text: string): boolean {
|
|
154
156
|
// Must contain at least one FlowQuery keyword
|
|
155
|
-
return this.flowQueryKeywords.some(pattern => pattern.test(text));
|
|
157
|
+
return this.flowQueryKeywords.some((pattern) => pattern.test(text));
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
/**
|
|
159
161
|
* Try to extract a FlowQuery statement that's not in a code block.
|
|
160
162
|
*/
|
|
161
163
|
private extractInlineQuery(text: string): string | null {
|
|
162
|
-
const lines = text.split(
|
|
164
|
+
const lines = text.split("\n");
|
|
163
165
|
const queryLines: string[] = [];
|
|
164
166
|
let inQuery = false;
|
|
165
167
|
|
|
166
168
|
for (const line of lines) {
|
|
167
169
|
const trimmedLine = line.trim();
|
|
168
|
-
|
|
170
|
+
|
|
169
171
|
if (!inQuery && this.startKeywords.test(trimmedLine)) {
|
|
170
172
|
inQuery = true;
|
|
171
173
|
queryLines.push(trimmedLine);
|
|
172
174
|
} else if (inQuery) {
|
|
173
175
|
// Check if this line continues the query
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
trimmedLine.startsWith(
|
|
177
|
-
trimmedLine
|
|
176
|
+
if (
|
|
177
|
+
this.continueKeywords.test(trimmedLine) ||
|
|
178
|
+
trimmedLine.startsWith("{") ||
|
|
179
|
+
trimmedLine.startsWith("}") ||
|
|
180
|
+
trimmedLine === "" ||
|
|
178
181
|
/^[A-Za-z_][A-Za-z0-9_]*\./.test(trimmedLine) ||
|
|
179
|
-
/^\s+/.test(line)
|
|
182
|
+
/^\s+/.test(line)
|
|
183
|
+
) {
|
|
184
|
+
// Indented line
|
|
180
185
|
queryLines.push(trimmedLine);
|
|
181
186
|
} else {
|
|
182
187
|
// End of query
|
|
@@ -185,7 +190,7 @@ export class FlowQueryExtractor {
|
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
|
|
188
|
-
const query = queryLines.join(
|
|
193
|
+
const query = queryLines.join("\n").trim();
|
|
189
194
|
return query && this.isLikelyFlowQuery(query) ? query : null;
|
|
190
195
|
}
|
|
191
196
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM (Large Language Model) Client
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for making OpenAI-compatible API calls.
|
|
5
|
+
* Supports both regular and streaming responses.
|
|
6
|
+
*/
|
|
7
|
+
import { getStoredApiConfig } from "../components/ApiKeySettings";
|
|
8
|
+
|
|
9
|
+
export interface LlmOptions {
|
|
10
|
+
/** System prompt to use */
|
|
11
|
+
systemPrompt?: string;
|
|
12
|
+
/** Conversation history */
|
|
13
|
+
messages?: Array<{ role: "user" | "assistant" | "system"; content: string }>;
|
|
14
|
+
/** Model to use (defaults to stored model or gpt-4o-mini) */
|
|
15
|
+
model?: string;
|
|
16
|
+
/** Temperature for response generation (0-2, default 0.7) */
|
|
17
|
+
temperature?: number;
|
|
18
|
+
/** Maximum tokens in response */
|
|
19
|
+
maxTokens?: number;
|
|
20
|
+
/** API key override (uses stored key if not provided) */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** Organization ID override (uses stored org if not provided) */
|
|
23
|
+
organizationId?: string;
|
|
24
|
+
/** Base URL for API (defaults to OpenAI) */
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LlmMessage {
|
|
29
|
+
role: "user" | "assistant" | "system";
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface LlmChoice {
|
|
34
|
+
index: number;
|
|
35
|
+
message: LlmMessage;
|
|
36
|
+
finish_reason: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface LlmResponse {
|
|
40
|
+
id: string;
|
|
41
|
+
object: string;
|
|
42
|
+
created: number;
|
|
43
|
+
model: string;
|
|
44
|
+
choices: LlmChoice[];
|
|
45
|
+
usage?: {
|
|
46
|
+
prompt_tokens: number;
|
|
47
|
+
completion_tokens: number;
|
|
48
|
+
total_tokens: number;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface LlmStreamChoice {
|
|
53
|
+
index: number;
|
|
54
|
+
delta: {
|
|
55
|
+
role?: string;
|
|
56
|
+
content?: string;
|
|
57
|
+
};
|
|
58
|
+
finish_reason: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LlmStreamChunk {
|
|
62
|
+
id: string;
|
|
63
|
+
object: string;
|
|
64
|
+
created: number;
|
|
65
|
+
model: string;
|
|
66
|
+
choices: LlmStreamChoice[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the API configuration, merging stored config with provided options.
|
|
71
|
+
*/
|
|
72
|
+
function getApiConfig(options: LlmOptions): {
|
|
73
|
+
apiKey: string;
|
|
74
|
+
orgId?: string;
|
|
75
|
+
model: string;
|
|
76
|
+
baseUrl: string;
|
|
77
|
+
} {
|
|
78
|
+
const storedConfig = getStoredApiConfig();
|
|
79
|
+
|
|
80
|
+
const apiKey = options.apiKey || storedConfig?.apiKey;
|
|
81
|
+
if (!apiKey) {
|
|
82
|
+
throw new Error("No API key configured. Please set your OpenAI API key in settings.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
apiKey,
|
|
87
|
+
orgId: options.organizationId || storedConfig?.organizationId,
|
|
88
|
+
model: options.model || storedConfig?.model || "gpt-4o-mini",
|
|
89
|
+
baseUrl: options.baseUrl || "https://api.openai.com/v1",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build the messages array for the API request.
|
|
95
|
+
*/
|
|
96
|
+
function buildMessages(content: string, options: LlmOptions): LlmMessage[] {
|
|
97
|
+
const messages: LlmMessage[] = [];
|
|
98
|
+
|
|
99
|
+
// Add system prompt if provided
|
|
100
|
+
if (options.systemPrompt) {
|
|
101
|
+
messages.push({
|
|
102
|
+
role: "system",
|
|
103
|
+
content: options.systemPrompt,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add conversation history if provided
|
|
108
|
+
if (options.messages && options.messages.length > 0) {
|
|
109
|
+
messages.push(...options.messages);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add the current user message
|
|
113
|
+
messages.push({
|
|
114
|
+
role: "user",
|
|
115
|
+
content,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return messages;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Make a non-streaming LLM API call.
|
|
123
|
+
*
|
|
124
|
+
* @param content - The user's message content
|
|
125
|
+
* @param options - LLM options including system prompt and conversation history
|
|
126
|
+
* @returns Promise resolving to the LLM response
|
|
127
|
+
*/
|
|
128
|
+
export async function llm(content: string, options: LlmOptions = {}): Promise<LlmResponse> {
|
|
129
|
+
const config = getApiConfig(options);
|
|
130
|
+
const messages = buildMessages(content, options);
|
|
131
|
+
|
|
132
|
+
const headers: Record<string, string> = {
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (config.orgId) {
|
|
138
|
+
headers["OpenAI-Organization"] = config.orgId;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const response = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
model: config.model,
|
|
146
|
+
messages,
|
|
147
|
+
temperature: options.temperature ?? 0.7,
|
|
148
|
+
max_tokens: options.maxTokens,
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const errorBody = await response.text();
|
|
154
|
+
throw new Error(`LLM API error (${response.status}): ${errorBody}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return response.json();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Make a streaming LLM API call.
|
|
162
|
+
*
|
|
163
|
+
* @param content - The user's message content
|
|
164
|
+
* @param options - LLM options including system prompt and conversation history
|
|
165
|
+
* @yields LLM stream chunks as they arrive
|
|
166
|
+
*/
|
|
167
|
+
export async function* llmStream(
|
|
168
|
+
content: string,
|
|
169
|
+
options: LlmOptions = {}
|
|
170
|
+
): AsyncGenerator<LlmStreamChunk, void, unknown> {
|
|
171
|
+
const config = getApiConfig(options);
|
|
172
|
+
const messages = buildMessages(content, options);
|
|
173
|
+
|
|
174
|
+
const headers: Record<string, string> = {
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (config.orgId) {
|
|
180
|
+
headers["OpenAI-Organization"] = config.orgId;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const response = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers,
|
|
186
|
+
body: JSON.stringify({
|
|
187
|
+
model: config.model,
|
|
188
|
+
messages,
|
|
189
|
+
temperature: options.temperature ?? 0.7,
|
|
190
|
+
max_tokens: options.maxTokens,
|
|
191
|
+
stream: true,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
const errorBody = await response.text();
|
|
197
|
+
throw new Error(`LLM API error (${response.status}): ${errorBody}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!response.body) {
|
|
201
|
+
throw new Error("Response body is null");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const reader = response.body.getReader();
|
|
205
|
+
const decoder = new TextDecoder();
|
|
206
|
+
let buffer = "";
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
while (true) {
|
|
210
|
+
const { done, value } = await reader.read();
|
|
211
|
+
|
|
212
|
+
if (done) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
buffer += decoder.decode(value, { stream: true });
|
|
217
|
+
|
|
218
|
+
// Process complete SSE messages
|
|
219
|
+
const lines = buffer.split("\n");
|
|
220
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
221
|
+
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
const trimmedLine = line.trim();
|
|
224
|
+
|
|
225
|
+
if (!trimmedLine || trimmedLine === ":") {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (trimmedLine === "data: [DONE]") {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (trimmedLine.startsWith("data: ")) {
|
|
234
|
+
const jsonStr = trimmedLine.slice(6);
|
|
235
|
+
try {
|
|
236
|
+
const chunk = JSON.parse(jsonStr) as LlmStreamChunk;
|
|
237
|
+
yield chunk;
|
|
238
|
+
} catch {
|
|
239
|
+
// Skip invalid JSON chunks
|
|
240
|
+
console.warn("Failed to parse LLM stream chunk:", jsonStr);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} finally {
|
|
246
|
+
reader.releaseLock();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Utils module exports
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export { FlowQueryExecutor } from
|
|
6
|
-
export type { FlowQueryExecutionResult } from
|
|
5
|
+
export { FlowQueryExecutor } from "./FlowQueryExecutor";
|
|
6
|
+
export type { FlowQueryExecutionResult } from "./FlowQueryExecutor";
|
|
7
7
|
|
|
8
|
-
export { FlowQueryExtractor, extractFlowQuery, extractAllFlowQueries } from
|
|
9
|
-
export type { FlowQueryExtraction } from
|
|
8
|
+
export { FlowQueryExtractor, extractFlowQuery, extractAllFlowQueries } from "./FlowQueryExtractor";
|
|
9
|
+
export type { FlowQueryExtraction } from "./FlowQueryExtractor";
|
|
10
|
+
|
|
11
|
+
export { llm, llmStream } from "./Llm";
|
|
12
|
+
export type { LlmOptions, LlmResponse, LlmStreamChunk } from "./Llm";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"outDir": "./dist",
|
|
4
|
-
"rootDir": "
|
|
4
|
+
"rootDir": ".",
|
|
5
5
|
"target": "ES2020",
|
|
6
6
|
"module": "ESNext",
|
|
7
7
|
"moduleResolution": "bundler",
|
|
8
|
+
"experimentalDecorators": true,
|
|
8
9
|
"esModuleInterop": true,
|
|
9
10
|
"forceConsistentCasingInFileNames": true,
|
|
10
11
|
"strict": true,
|
|
@@ -14,8 +15,8 @@
|
|
|
14
15
|
"resolveJsonModule": true,
|
|
15
16
|
"jsx": "react-jsx",
|
|
16
17
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
17
|
-
"types": ["node"]
|
|
18
|
+
"types": ["node", "jest"]
|
|
18
19
|
},
|
|
19
|
-
"include": ["src/**/*"],
|
|
20
|
+
"include": ["src/**/*", "data/**/*"],
|
|
20
21
|
"exclude": ["node_modules", "dist"]
|
|
21
22
|
}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
const path = require(
|
|
2
|
-
const HtmlWebpackPlugin = require(
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
3
3
|
|
|
4
4
|
module.exports = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
5
|
+
mode: "development",
|
|
6
|
+
entry: "./src/index.tsx",
|
|
7
|
+
target: "web",
|
|
8
|
+
output: {
|
|
9
|
+
filename: "bundle.js",
|
|
10
|
+
path: path.resolve(__dirname, "dist"),
|
|
11
|
+
clean: true,
|
|
12
|
+
},
|
|
13
|
+
resolve: {
|
|
14
|
+
extensions: [".tsx", ".ts", ".js", ".jsx"],
|
|
15
|
+
},
|
|
16
|
+
module: {
|
|
17
|
+
rules: [
|
|
18
|
+
{
|
|
19
|
+
test: /\.tsx?$/,
|
|
20
|
+
use: "ts-loader",
|
|
21
|
+
exclude: /node_modules/,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
test: /\.css$/,
|
|
25
|
+
use: ["style-loader", "css-loader"],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
plugins: [
|
|
30
|
+
new HtmlWebpackPlugin({
|
|
31
|
+
template: "./public/index.html",
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
devServer: {
|
|
35
|
+
static: ["./dist", { directory: path.resolve(__dirname, "data"), publicPath: "/data" }],
|
|
36
|
+
port: 3000,
|
|
37
|
+
open: true,
|
|
38
|
+
hot: true,
|
|
39
|
+
},
|
|
40
|
+
optimization: {
|
|
41
|
+
minimize: false,
|
|
42
|
+
},
|
|
43
43
|
};
|