flowquery 1.0.5 → 1.0.7

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 (138) hide show
  1. package/README.md +74 -0
  2. package/dist/compute/runner.d.ts +1 -22
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js.map +1 -1
  5. package/dist/extensibility.d.ts +35 -0
  6. package/dist/extensibility.d.ts.map +1 -0
  7. package/dist/extensibility.js +49 -0
  8. package/dist/extensibility.js.map +1 -0
  9. package/dist/flowquery.min.js +1 -1
  10. package/dist/index.browser.d.ts.map +1 -1
  11. package/dist/index.browser.js +0 -80
  12. package/dist/index.browser.js.map +1 -1
  13. package/dist/index.node.d.ts +3 -3
  14. package/dist/index.node.d.ts.map +1 -1
  15. package/dist/index.node.js +0 -80
  16. package/dist/index.node.js.map +1 -1
  17. package/dist/parsing/functions/avg.d.ts.map +1 -1
  18. package/dist/parsing/functions/avg.js +20 -2
  19. package/dist/parsing/functions/avg.js.map +1 -1
  20. package/dist/parsing/functions/collect.d.ts.map +1 -1
  21. package/dist/parsing/functions/collect.js +20 -2
  22. package/dist/parsing/functions/collect.js.map +1 -1
  23. package/dist/parsing/functions/function_factory.d.ts +26 -80
  24. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  25. package/dist/parsing/functions/function_factory.js +46 -168
  26. package/dist/parsing/functions/function_factory.js.map +1 -1
  27. package/dist/parsing/functions/function_metadata.d.ts +81 -20
  28. package/dist/parsing/functions/function_metadata.d.ts.map +1 -1
  29. package/dist/parsing/functions/function_metadata.js +154 -152
  30. package/dist/parsing/functions/function_metadata.js.map +1 -1
  31. package/dist/parsing/functions/functions.d.ts.map +1 -1
  32. package/dist/parsing/functions/functions.js +37 -2
  33. package/dist/parsing/functions/functions.js.map +1 -1
  34. package/dist/parsing/functions/join.d.ts.map +1 -1
  35. package/dist/parsing/functions/join.js +21 -2
  36. package/dist/parsing/functions/join.js.map +1 -1
  37. package/dist/parsing/functions/predicate_function.d.ts +1 -0
  38. package/dist/parsing/functions/predicate_function.d.ts.map +1 -1
  39. package/dist/parsing/functions/predicate_function.js +3 -0
  40. package/dist/parsing/functions/predicate_function.js.map +1 -1
  41. package/dist/parsing/functions/predicate_sum.d.ts.map +1 -1
  42. package/dist/parsing/functions/predicate_sum.js +23 -2
  43. package/dist/parsing/functions/predicate_sum.js.map +1 -1
  44. package/dist/parsing/functions/rand.d.ts.map +1 -1
  45. package/dist/parsing/functions/rand.js +18 -2
  46. package/dist/parsing/functions/rand.js.map +1 -1
  47. package/dist/parsing/functions/range.d.ts.map +1 -1
  48. package/dist/parsing/functions/range.js +21 -2
  49. package/dist/parsing/functions/range.js.map +1 -1
  50. package/dist/parsing/functions/replace.d.ts.map +1 -1
  51. package/dist/parsing/functions/replace.js +22 -2
  52. package/dist/parsing/functions/replace.js.map +1 -1
  53. package/dist/parsing/functions/round.d.ts.map +1 -1
  54. package/dist/parsing/functions/round.js +20 -2
  55. package/dist/parsing/functions/round.js.map +1 -1
  56. package/dist/parsing/functions/size.d.ts.map +1 -1
  57. package/dist/parsing/functions/size.js +20 -2
  58. package/dist/parsing/functions/size.js.map +1 -1
  59. package/dist/parsing/functions/split.d.ts.map +1 -1
  60. package/dist/parsing/functions/split.js +21 -2
  61. package/dist/parsing/functions/split.js.map +1 -1
  62. package/dist/parsing/functions/stringify.d.ts.map +1 -1
  63. package/dist/parsing/functions/stringify.js +20 -2
  64. package/dist/parsing/functions/stringify.js.map +1 -1
  65. package/dist/parsing/functions/sum.d.ts.map +1 -1
  66. package/dist/parsing/functions/sum.js +20 -2
  67. package/dist/parsing/functions/sum.js.map +1 -1
  68. package/dist/parsing/functions/to_json.d.ts.map +1 -1
  69. package/dist/parsing/functions/to_json.js +20 -2
  70. package/dist/parsing/functions/to_json.js.map +1 -1
  71. package/dist/parsing/parser.d.ts.map +1 -1
  72. package/dist/parsing/parser.js +1 -2
  73. package/dist/parsing/parser.js.map +1 -1
  74. package/docs/flowquery.min.js +1 -1
  75. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  76. package/misc/apps/RAG/.env.example +14 -0
  77. package/misc/apps/RAG/README.md +0 -7
  78. package/misc/apps/RAG/package.json +16 -7
  79. package/misc/apps/RAG/public/index.html +18 -0
  80. package/misc/apps/RAG/src/App.css +42 -0
  81. package/misc/apps/RAG/src/App.tsx +50 -0
  82. package/misc/apps/RAG/src/components/ApiKeySettings.tsx +245 -0
  83. package/misc/apps/RAG/src/components/ChatContainer.css +67 -0
  84. package/misc/apps/RAG/src/components/ChatContainer.tsx +239 -0
  85. package/misc/apps/RAG/src/components/ChatInput.css +23 -0
  86. package/misc/apps/RAG/src/components/ChatInput.tsx +62 -0
  87. package/misc/apps/RAG/src/components/ChatMessage.css +136 -0
  88. package/misc/apps/RAG/src/components/ChatMessage.tsx +152 -0
  89. package/misc/apps/RAG/src/components/FlowQueryAgent.ts +390 -0
  90. package/misc/apps/RAG/src/components/FlowQueryRunner.css +104 -0
  91. package/misc/apps/RAG/src/components/FlowQueryRunner.tsx +332 -0
  92. package/misc/apps/RAG/src/components/index.ts +15 -0
  93. package/misc/apps/RAG/src/index.tsx +17 -0
  94. package/misc/apps/RAG/src/plugins/README.md +139 -0
  95. package/misc/apps/RAG/src/plugins/index.ts +68 -0
  96. package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +75 -0
  97. package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +67 -0
  98. package/misc/apps/RAG/src/plugins/loaders/Llm.ts +437 -0
  99. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +151 -0
  100. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +385 -0
  101. package/misc/apps/RAG/src/prompts/index.ts +10 -0
  102. package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +131 -0
  103. package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +203 -0
  104. package/misc/apps/RAG/src/utils/index.ts +9 -0
  105. package/misc/apps/RAG/tsconfig.json +4 -2
  106. package/misc/apps/RAG/webpack.config.js +23 -12
  107. package/package.json +7 -1
  108. package/src/compute/runner.ts +1 -26
  109. package/src/extensibility.ts +45 -0
  110. package/src/index.browser.ts +2 -88
  111. package/src/index.node.ts +3 -92
  112. package/src/parsing/functions/avg.ts +10 -0
  113. package/src/parsing/functions/collect.ts +10 -0
  114. package/src/parsing/functions/function_factory.ts +56 -194
  115. package/src/parsing/functions/function_metadata.ts +187 -168
  116. package/src/parsing/functions/functions.ts +27 -0
  117. package/src/parsing/functions/join.ts +11 -0
  118. package/src/parsing/functions/predicate_function.ts +4 -0
  119. package/src/parsing/functions/predicate_sum.ts +13 -0
  120. package/src/parsing/functions/rand.ts +8 -0
  121. package/src/parsing/functions/range.ts +11 -0
  122. package/src/parsing/functions/replace.ts +12 -0
  123. package/src/parsing/functions/round.ts +10 -0
  124. package/src/parsing/functions/size.ts +10 -0
  125. package/src/parsing/functions/split.ts +11 -0
  126. package/src/parsing/functions/stringify.ts +10 -0
  127. package/src/parsing/functions/sum.ts +10 -0
  128. package/src/parsing/functions/to_json.ts +10 -0
  129. package/src/parsing/parser.ts +1 -2
  130. package/tests/extensibility.test.ts +563 -0
  131. package/tsconfig.json +1 -0
  132. package/dist/parsing/functions/predicate_function_factory.d.ts +0 -6
  133. package/dist/parsing/functions/predicate_function_factory.d.ts.map +0 -1
  134. package/dist/parsing/functions/predicate_function_factory.js +0 -19
  135. package/dist/parsing/functions/predicate_function_factory.js.map +0 -1
  136. package/misc/apps/RAG/src/index.ts +0 -20
  137. package/src/parsing/functions/predicate_function_factory.ts +0 -15
  138. package/tests/parsing/function_plugins.test.ts +0 -369
@@ -0,0 +1,136 @@
1
+ .chat-message {
2
+ display: flex;
3
+ gap: 12px;
4
+ padding: 16px;
5
+ border-radius: 8px;
6
+ margin-bottom: 8px;
7
+ }
8
+
9
+ .chat-message-user {
10
+ background-color: #e8f4fd;
11
+ }
12
+
13
+ .chat-message-assistant {
14
+ background-color: #f5f5f5;
15
+ }
16
+
17
+ .chat-message-avatar {
18
+ width: 32px;
19
+ height: 32px;
20
+ border-radius: 50%;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ flex-shrink: 0;
25
+ font-size: 18px;
26
+ }
27
+
28
+ .chat-message-user .chat-message-avatar {
29
+ background-color: #0078d4;
30
+ color: white;
31
+ }
32
+
33
+ .chat-message-assistant .chat-message-avatar {
34
+ background-color: #6b6b6b;
35
+ color: white;
36
+ }
37
+
38
+ .chat-message-content {
39
+ flex: 1;
40
+ min-width: 0;
41
+ }
42
+
43
+ .chat-message-header {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 8px;
47
+ margin-bottom: 4px;
48
+ }
49
+
50
+ .chat-message-role {
51
+ font-weight: 600;
52
+ }
53
+
54
+ .chat-message-time {
55
+ font-size: 12px;
56
+ color: #666;
57
+ }
58
+
59
+ .chat-message-text {
60
+ white-space: pre-wrap;
61
+ word-break: break-word;
62
+ line-height: 1.5;
63
+ }
64
+
65
+ .streaming-indicator {
66
+ margin-left: 8px;
67
+ display: inline-block;
68
+ vertical-align: middle;
69
+ }
70
+
71
+ /* FlowQuery code block styling */
72
+ .flowquery-code-block {
73
+ margin: 12px 0;
74
+ border: 1px solid #e0e0e0;
75
+ border-radius: 6px;
76
+ overflow: hidden;
77
+ background-color: #f8f8f8;
78
+ }
79
+
80
+ .flowquery-code-header {
81
+ display: flex;
82
+ justify-content: space-between;
83
+ align-items: center;
84
+ padding: 6px 12px;
85
+ background-color: #e8e8e8;
86
+ border-bottom: 1px solid #e0e0e0;
87
+ }
88
+
89
+ .flowquery-code-label {
90
+ font-size: 11px;
91
+ font-weight: 600;
92
+ color: #666;
93
+ text-transform: uppercase;
94
+ letter-spacing: 0.5px;
95
+ }
96
+
97
+ .flowquery-code-actions {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 8px;
101
+ }
102
+
103
+ .flowquery-run-link {
104
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
105
+ font-size: 13px;
106
+ font-weight: 600;
107
+ color: #0078d4;
108
+ text-decoration: none;
109
+ cursor: pointer;
110
+ padding: 2px 6px;
111
+ border-radius: 4px;
112
+ transition: background-color 0.15s ease;
113
+ }
114
+
115
+ .flowquery-run-link:hover {
116
+ background-color: rgba(0, 120, 212, 0.1);
117
+ text-decoration: none;
118
+ }
119
+
120
+ .flowquery-run-button {
121
+ font-size: 11px;
122
+ }
123
+
124
+ .flowquery-code-content {
125
+ margin: 0;
126
+ padding: 12px;
127
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
128
+ font-size: 13px;
129
+ line-height: 1.4;
130
+ overflow-x: auto;
131
+ white-space: pre-wrap;
132
+ }
133
+
134
+ .flowquery-code-content code {
135
+ font-family: inherit;
136
+ }
@@ -0,0 +1,152 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import { Body1, Spinner, Button, Tooltip } from '@fluentui/react-components';
3
+ import { PersonFilled, BotFilled, Play16Regular } from '@fluentui/react-icons';
4
+ import { FlowQueryRunner } from './FlowQueryRunner';
5
+ import './ChatMessage.css';
6
+
7
+ export interface Message {
8
+ id: string;
9
+ role: 'user' | 'assistant';
10
+ content: string;
11
+ timestamp: Date;
12
+ isStreaming?: boolean;
13
+ }
14
+
15
+ interface ChatMessageProps {
16
+ message: Message;
17
+ }
18
+
19
+ /**
20
+ * Extract FlowQuery code blocks from markdown content.
21
+ * Looks for ```flowquery ... ``` code blocks.
22
+ */
23
+ function extractFlowQueryBlocks(content: string): string[] {
24
+ const regex = /```flowquery\n([\s\S]*?)```/gi;
25
+ const matches: string[] = [];
26
+ let match;
27
+
28
+ while ((match = regex.exec(content)) !== null) {
29
+ if (match[1]?.trim()) {
30
+ matches.push(match[1].trim());
31
+ }
32
+ }
33
+
34
+ return matches;
35
+ }
36
+
37
+ /**
38
+ * Renders message content with FlowQuery code blocks enhanced with run buttons.
39
+ */
40
+ const MessageContent: React.FC<{ content: string; isStreaming?: boolean }> = ({ content, isStreaming }) => {
41
+ const [runnerQuery, setRunnerQuery] = useState<string | null>(null);
42
+
43
+ const flowQueryBlocks = useMemo(() => extractFlowQueryBlocks(content), [content]);
44
+
45
+ // If there are no FlowQuery blocks, render plain content
46
+ if (flowQueryBlocks.length === 0) {
47
+ return (
48
+ <>
49
+ {content}
50
+ {isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
51
+ </>
52
+ );
53
+ }
54
+
55
+ // Split content by FlowQuery code blocks and render with buttons
56
+ const parts: React.ReactNode[] = [];
57
+ let lastIndex = 0;
58
+ const regex = /```flowquery\n([\s\S]*?)```/gi;
59
+ let match;
60
+ let partIndex = 0;
61
+
62
+ while ((match = regex.exec(content)) !== null) {
63
+ // Add text before the code block
64
+ if (match.index > lastIndex) {
65
+ parts.push(
66
+ <span key={`text-${partIndex}`}>
67
+ {content.slice(lastIndex, match.index)}
68
+ </span>
69
+ );
70
+ }
71
+
72
+ const query = match[1]?.trim() || '';
73
+
74
+ // Add the code block with a run button and </> link
75
+ parts.push(
76
+ <div key={`code-${partIndex}`} className="flowquery-code-block">
77
+ <div className="flowquery-code-header">
78
+ <span className="flowquery-code-label">flowquery</span>
79
+ <div className="flowquery-code-actions">
80
+ <Tooltip content="Run in FlowQuery Runner" relationship="label">
81
+ <Button
82
+ appearance="subtle"
83
+ size="small"
84
+ icon={<Play16Regular />}
85
+ className="flowquery-run-button"
86
+ onClick={() => setRunnerQuery(query)}
87
+ >
88
+ Run
89
+ </Button>
90
+ </Tooltip>
91
+ </div>
92
+ </div>
93
+ <pre className="flowquery-code-content">
94
+ <code>{query}</code>
95
+ </pre>
96
+ </div>
97
+ );
98
+
99
+ lastIndex = match.index + match[0].length;
100
+ partIndex++;
101
+ }
102
+
103
+ // Add remaining text after the last code block
104
+ if (lastIndex < content.length) {
105
+ parts.push(
106
+ <span key={`text-${partIndex}`}>
107
+ {content.slice(lastIndex)}
108
+ </span>
109
+ );
110
+ }
111
+
112
+ return (
113
+ <>
114
+ {parts}
115
+ {isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
116
+ {runnerQuery !== null && (
117
+ <FlowQueryRunner
118
+ initialQuery={runnerQuery}
119
+ open={true}
120
+ onOpenChange={(open) => {
121
+ if (!open) setRunnerQuery(null);
122
+ }}
123
+ />
124
+ )}
125
+ </>
126
+ );
127
+ };
128
+
129
+ export const ChatMessage: React.FC<ChatMessageProps> = ({ message }) => {
130
+ const isUser = message.role === 'user';
131
+
132
+ return (
133
+ <div className={`chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'}`}>
134
+ <div className="chat-message-avatar">
135
+ {isUser ? <PersonFilled /> : <BotFilled />}
136
+ </div>
137
+ <div className="chat-message-content">
138
+ <div className="chat-message-header">
139
+ <Body1 className="chat-message-role">
140
+ {isUser ? 'You' : 'Assistant'}
141
+ </Body1>
142
+ <span className="chat-message-time">
143
+ {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
144
+ </span>
145
+ </div>
146
+ <div className="chat-message-text">
147
+ <MessageContent content={message.content} isStreaming={message.isStreaming} />
148
+ </div>
149
+ </div>
150
+ </div>
151
+ );
152
+ };
@@ -0,0 +1,390 @@
1
+ /**
2
+ * FlowQuery Agent
3
+ *
4
+ * Orchestrates the multi-step flow:
5
+ * 1. Send user query to LLM to generate a FlowQuery statement
6
+ * 2. Execute the FlowQuery statement
7
+ * 3. Send results back to LLM for interpretation
8
+ * 4. Return final response to user
9
+ */
10
+
11
+ import { llm, llmStream, LlmOptions, LlmResponse } from '../plugins/loaders/Llm';
12
+ import { FlowQueryExecutor, FlowQueryExecutionResult } from '../utils/FlowQueryExecutor';
13
+ import { extractFlowQuery, FlowQueryExtraction } from '../utils/FlowQueryExtractor';
14
+
15
+ // Shared executor instance
16
+ const flowQueryExecutor = new FlowQueryExecutor();
17
+ import { generateInterpretationPrompt } from '../prompts';
18
+
19
+ /**
20
+ * Represents a step in the agent's execution process.
21
+ */
22
+ export interface AgentStep {
23
+ type: 'query_generation' | 'query_execution' | 'interpretation' | 'direct_response';
24
+ content: string;
25
+ timestamp: Date;
26
+ metadata?: {
27
+ query?: string;
28
+ executionResult?: FlowQueryExecutionResult;
29
+ extraction?: FlowQueryExtraction;
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Result of the agent's processing.
35
+ */
36
+ export interface AgentResult {
37
+ /** Final response text to show the user */
38
+ finalResponse: string;
39
+ /** Steps taken during processing (for debugging/transparency) */
40
+ steps: AgentStep[];
41
+ /** Whether processing was successful */
42
+ success: boolean;
43
+ /** Error message if processing failed */
44
+ error?: string;
45
+ }
46
+
47
+ /**
48
+ * Callback for streaming agent responses.
49
+ */
50
+ export type AgentStreamCallback = (chunk: string, step: AgentStep['type']) => void;
51
+
52
+ /**
53
+ * Options for the FlowQuery agent.
54
+ */
55
+ export interface FlowQueryAgentOptions {
56
+ /** System prompt for query generation */
57
+ systemPrompt: string;
58
+ /** LLM options to use */
59
+ llmOptions?: LlmOptions;
60
+ /** Conversation history */
61
+ conversationHistory?: Array<{ role: 'user' | 'assistant'; content: string }>;
62
+ /** Callback for streaming responses */
63
+ onStream?: AgentStreamCallback;
64
+ /** Whether to show intermediate steps to the user */
65
+ showIntermediateSteps?: boolean;
66
+ /** Maximum number of retry attempts for query execution */
67
+ maxRetries?: number;
68
+ }
69
+
70
+ /**
71
+ * Process a user query through the FlowQuery agent.
72
+ *
73
+ * @param userQuery - The natural language query from the user
74
+ * @param options - Agent configuration options
75
+ * @returns The agent result including final response and steps taken
76
+ */
77
+ export async function processQuery(
78
+ userQuery: string,
79
+ options: FlowQueryAgentOptions
80
+ ): Promise<AgentResult> {
81
+ const steps: AgentStep[] = [];
82
+ const { systemPrompt, llmOptions = {}, conversationHistory = [], onStream, showIntermediateSteps = true } = options;
83
+
84
+ try {
85
+ // Step 1: Generate FlowQuery from natural language
86
+ const generationResponse = await llm(userQuery, {
87
+ ...llmOptions,
88
+ systemPrompt,
89
+ messages: conversationHistory,
90
+ });
91
+
92
+ const generationContent = generationResponse.choices[0]?.message?.content || '';
93
+
94
+ steps.push({
95
+ type: 'query_generation',
96
+ content: generationContent,
97
+ timestamp: new Date(),
98
+ });
99
+
100
+ // Step 2: Extract the FlowQuery from the response
101
+ const extraction = extractFlowQuery(generationContent);
102
+
103
+ // If no query needed (direct response from LLM)
104
+ if (extraction.noQueryNeeded || !extraction.found) {
105
+ const directResponse = extraction.directResponse || generationContent;
106
+
107
+ steps.push({
108
+ type: 'direct_response',
109
+ content: directResponse,
110
+ timestamp: new Date(),
111
+ metadata: { extraction }
112
+ });
113
+
114
+ return {
115
+ finalResponse: directResponse,
116
+ steps,
117
+ success: true,
118
+ };
119
+ }
120
+
121
+ // Step 3: Execute the FlowQuery
122
+ const executionResult = await flowQueryExecutor.execute(extraction.query!);
123
+
124
+ steps.push({
125
+ type: 'query_execution',
126
+ content: flowQueryExecutor.formatResult(executionResult),
127
+ timestamp: new Date(),
128
+ metadata: {
129
+ query: extraction.query!,
130
+ executionResult,
131
+ extraction
132
+ }
133
+ });
134
+
135
+ // If execution failed, ask LLM to fix the query or explain the error
136
+ if (!executionResult.success) {
137
+ const errorInterpretation = await interpretError(
138
+ userQuery,
139
+ extraction.query!,
140
+ executionResult,
141
+ options
142
+ );
143
+
144
+ return {
145
+ finalResponse: errorInterpretation,
146
+ steps,
147
+ success: false,
148
+ error: executionResult.error
149
+ };
150
+ }
151
+
152
+ // Step 4: Send results to LLM for interpretation
153
+ const interpretationPrompt = buildInterpretationPrompt(
154
+ userQuery,
155
+ extraction.query!,
156
+ executionResult
157
+ );
158
+
159
+ let finalResponse = '';
160
+
161
+ if (onStream) {
162
+ // Stream the interpretation response
163
+ for await (const chunk of llmStream(interpretationPrompt, {
164
+ ...llmOptions,
165
+ systemPrompt: generateInterpretationPrompt(),
166
+ messages: conversationHistory,
167
+ })) {
168
+ const deltaContent = chunk.choices?.[0]?.delta?.content || '';
169
+ if (deltaContent) {
170
+ finalResponse += deltaContent;
171
+ onStream(deltaContent, 'interpretation');
172
+ }
173
+ }
174
+ } else {
175
+ const interpretationResponse = await llm(interpretationPrompt, {
176
+ ...llmOptions,
177
+ systemPrompt: generateInterpretationPrompt(),
178
+ messages: conversationHistory,
179
+ });
180
+ finalResponse = interpretationResponse.choices[0]?.message?.content || '';
181
+ }
182
+
183
+ steps.push({
184
+ type: 'interpretation',
185
+ content: finalResponse,
186
+ timestamp: new Date(),
187
+ });
188
+
189
+ // Build the complete response with optional intermediate steps
190
+ let completeResponse = '';
191
+
192
+ if (showIntermediateSteps && extraction.explanation) {
193
+ completeResponse += extraction.explanation + '\n\n';
194
+ }
195
+
196
+ if (showIntermediateSteps) {
197
+ completeResponse += `**Query executed:**\n\`\`\`flowquery\n${extraction.query}\n\`\`\`\n\n`;
198
+ }
199
+
200
+ completeResponse += finalResponse;
201
+
202
+ return {
203
+ finalResponse: completeResponse,
204
+ steps,
205
+ success: true,
206
+ };
207
+ } catch (error) {
208
+ const errorMessage = error instanceof Error ? error.message : String(error);
209
+
210
+ return {
211
+ finalResponse: `⚠️ An error occurred: ${errorMessage}`,
212
+ steps,
213
+ success: false,
214
+ error: errorMessage,
215
+ };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Process a query with streaming support for the final interpretation.
221
+ */
222
+ export async function* processQueryStream(
223
+ userQuery: string,
224
+ options: FlowQueryAgentOptions
225
+ ): AsyncGenerator<{ chunk: string; step: AgentStep['type']; done: boolean; steps?: AgentStep[] }, void, unknown> {
226
+ const steps: AgentStep[] = [];
227
+ const { systemPrompt, llmOptions = {}, conversationHistory = [], showIntermediateSteps = true } = options;
228
+
229
+ try {
230
+ // Step 1: Generate FlowQuery from natural language (non-streaming for speed)
231
+ const generationResponse = await llm(userQuery, {
232
+ ...llmOptions,
233
+ systemPrompt,
234
+ messages: conversationHistory,
235
+ });
236
+
237
+ const generationContent = generationResponse.choices[0]?.message?.content || '';
238
+
239
+ steps.push({
240
+ type: 'query_generation',
241
+ content: generationContent,
242
+ timestamp: new Date(),
243
+ });
244
+
245
+ // Step 2: Extract the FlowQuery
246
+ const extraction = extractFlowQuery(generationContent);
247
+
248
+ // If no query needed
249
+ if (extraction.noQueryNeeded || !extraction.found) {
250
+ const directResponse = extraction.directResponse || generationContent;
251
+
252
+ steps.push({
253
+ type: 'direct_response',
254
+ content: directResponse,
255
+ timestamp: new Date(),
256
+ metadata: { extraction }
257
+ });
258
+
259
+ yield { chunk: directResponse, step: 'direct_response', done: true, steps };
260
+ return;
261
+ }
262
+
263
+ // Emit intermediate step: show the query being executed
264
+ if (showIntermediateSteps) {
265
+ yield {
266
+ chunk: `**Executing query:**\n\`\`\`flowquery\n${extraction.query}\n\`\`\`\n\n`,
267
+ step: 'query_generation',
268
+ done: false
269
+ };
270
+ }
271
+
272
+ // Step 3: Execute the FlowQuery
273
+ const executionResult = await flowQueryExecutor.execute(extraction.query!);
274
+
275
+ steps.push({
276
+ type: 'query_execution',
277
+ content: flowQueryExecutor.formatResult(executionResult),
278
+ timestamp: new Date(),
279
+ metadata: {
280
+ query: extraction.query!,
281
+ executionResult,
282
+ extraction
283
+ }
284
+ });
285
+
286
+ // Handle execution errors
287
+ if (!executionResult.success) {
288
+ const errorMessage = `⚠️ Query execution failed: ${executionResult.error}\n\nQuery attempted:\n\`\`\`flowquery\n${extraction.query}\n\`\`\``;
289
+ yield { chunk: errorMessage, step: 'query_execution', done: true, steps };
290
+ return;
291
+ }
292
+
293
+ // Step 4: Stream the interpretation
294
+ const interpretationPrompt = buildInterpretationPrompt(
295
+ userQuery,
296
+ extraction.query!,
297
+ executionResult
298
+ );
299
+
300
+ let interpretationContent = '';
301
+
302
+ for await (const chunk of llmStream(interpretationPrompt, {
303
+ ...llmOptions,
304
+ systemPrompt: generateInterpretationPrompt(),
305
+ messages: conversationHistory,
306
+ })) {
307
+ const deltaContent = chunk.choices?.[0]?.delta?.content || '';
308
+ if (deltaContent) {
309
+ interpretationContent += deltaContent;
310
+ yield { chunk: deltaContent, step: 'interpretation', done: false };
311
+ }
312
+ }
313
+
314
+ steps.push({
315
+ type: 'interpretation',
316
+ content: interpretationContent,
317
+ timestamp: new Date(),
318
+ });
319
+
320
+ yield { chunk: '', step: 'interpretation', done: true, steps };
321
+ } catch (error) {
322
+ const errorMessage = error instanceof Error ? error.message : String(error);
323
+ yield {
324
+ chunk: `⚠️ An error occurred: ${errorMessage}`,
325
+ step: 'interpretation',
326
+ done: true,
327
+ steps
328
+ };
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Build the prompt for the interpretation phase.
334
+ */
335
+ function buildInterpretationPrompt(
336
+ originalQuery: string,
337
+ flowQuery: string,
338
+ executionResult: FlowQueryExecutionResult
339
+ ): string {
340
+ const resultsJson = JSON.stringify(executionResult.results, null, 2);
341
+ const resultCount = executionResult.results?.length || 0;
342
+
343
+ return `The user asked: "${originalQuery}"
344
+
345
+ This was translated to the following FlowQuery:
346
+ \`\`\`flowquery
347
+ ${flowQuery}
348
+ \`\`\`
349
+
350
+ The query executed successfully in ${executionResult.executionTime.toFixed(2)}ms and returned ${resultCount} result(s):
351
+
352
+ \`\`\`json
353
+ ${resultsJson}
354
+ \`\`\`
355
+
356
+ Please interpret these results and provide a helpful response to the user's original question.`;
357
+ }
358
+
359
+ /**
360
+ * Handle execution errors by asking LLM to explain or suggest fixes.
361
+ */
362
+ async function interpretError(
363
+ originalQuery: string,
364
+ flowQuery: string,
365
+ executionResult: FlowQueryExecutionResult,
366
+ options: FlowQueryAgentOptions
367
+ ): Promise<string> {
368
+ const errorPrompt = `The user asked: "${originalQuery}"
369
+
370
+ This was translated to the following FlowQuery:
371
+ \`\`\`flowquery
372
+ ${flowQuery}
373
+ \`\`\`
374
+
375
+ However, the query failed with the following error:
376
+ ${executionResult.error}
377
+
378
+ Please explain what went wrong in user-friendly terms and, if possible, suggest how to fix the issue or rephrase their request.`;
379
+
380
+ const response = await llm(errorPrompt, {
381
+ ...options.llmOptions,
382
+ systemPrompt: 'You are a helpful assistant explaining query errors. Be concise and helpful.',
383
+ messages: options.conversationHistory,
384
+ });
385
+
386
+ return response.choices[0]?.message?.content ||
387
+ `The query failed with error: ${executionResult.error}`;
388
+ }
389
+
390
+ export default { processQuery, processQueryStream };