@finos/legend-lego 2.0.194 → 2.0.195
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/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/legend-ai/LegendAITypes.d.ts +0 -33
- package/lib/legend-ai/LegendAITypes.d.ts.map +1 -1
- package/lib/legend-ai/LegendAITypes.js +1 -39
- package/lib/legend-ai/LegendAITypes.js.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts +1 -96
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js +0 -56
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js.map +1 -1
- package/lib/legend-ai/__test-utils__/LegendAITestUtils.d.ts.map +1 -1
- package/lib/legend-ai/__test-utils__/LegendAITestUtils.js +0 -6
- package/lib/legend-ai/__test-utils__/LegendAITestUtils.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.d.ts +1 -2
- package/lib/legend-ai/components/LegendAIChat.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.js +10 -14
- package/lib/legend-ai/components/LegendAIChat.js.map +1 -1
- package/lib/legend-ai/index.d.ts +2 -5
- package/lib/legend-ai/index.d.ts.map +1 -1
- package/lib/legend-ai/index.js +2 -5
- package/lib/legend-ai/index.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatState.d.ts +5 -12
- package/lib/legend-ai/stores/LegendAIChatState.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatState.js +69 -604
- package/lib/legend-ai/stores/LegendAIChatState.js.map +1 -1
- package/package.json +5 -5
- package/src/legend-ai/LegendAITypes.ts +1 -51
- package/src/legend-ai/LegendAI_LegendApplicationPlugin_Extension.ts +0 -169
- package/src/legend-ai/__test-utils__/LegendAITestUtils.ts +0 -9
- package/src/legend-ai/components/LegendAIChat.tsx +26 -74
- package/src/legend-ai/index.ts +0 -18
- package/src/legend-ai/stores/LegendAIChatState.ts +128 -1039
- package/tsconfig.json +0 -3
- package/lib/legend-ai/components/LegendAIAnalysisPanel.d.ts +0 -24
- package/lib/legend-ai/components/LegendAIAnalysisPanel.d.ts.map +0 -1
- package/lib/legend-ai/components/LegendAIAnalysisPanel.js +0 -35
- package/lib/legend-ai/components/LegendAIAnalysisPanel.js.map +0 -1
- package/lib/legend-ai/components/LegendAIAnalysisUtils.d.ts +0 -23
- package/lib/legend-ai/components/LegendAIAnalysisUtils.d.ts.map +0 -1
- package/lib/legend-ai/components/LegendAIAnalysisUtils.js +0 -168
- package/lib/legend-ai/components/LegendAIAnalysisUtils.js.map +0 -1
- package/lib/legend-ai/components/LegendAICharts.d.ts +0 -25
- package/lib/legend-ai/components/LegendAICharts.d.ts.map +0 -1
- package/lib/legend-ai/components/LegendAICharts.js +0 -70
- package/lib/legend-ai/components/LegendAICharts.js.map +0 -1
- package/src/legend-ai/components/LegendAIAnalysisPanel.tsx +0 -102
- package/src/legend-ai/components/LegendAIAnalysisUtils.ts +0 -226
- package/src/legend-ai/components/LegendAICharts.tsx +0 -166
|
@@ -25,21 +25,16 @@ import {
|
|
|
25
25
|
type LegendAIMessage,
|
|
26
26
|
type LegendAIConversationTurn,
|
|
27
27
|
type LegendAIProductMetadata,
|
|
28
|
-
type LegendAIFallbackAction,
|
|
29
28
|
LegendAIQuestionIntent,
|
|
30
29
|
LegendAIThinkingStepStatus,
|
|
31
30
|
LegendAIMessageRole,
|
|
32
|
-
LegendAIErrorType,
|
|
33
|
-
LegendAIServiceError,
|
|
34
31
|
TDSServiceSourceType,
|
|
35
32
|
buildColumnDefsFromNames,
|
|
36
|
-
LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID,
|
|
37
33
|
} from '../LegendAITypes.js';
|
|
38
34
|
import {
|
|
39
35
|
type LegendAI_LegendApplicationPlugin_Extension,
|
|
40
36
|
type LegendAIOrchestratorDataProductCoordinates,
|
|
41
37
|
type LegendAISqlExecutionResultData,
|
|
42
|
-
type LegendAIResolvedEntities,
|
|
43
38
|
LegendAIJudgeVerdict,
|
|
44
39
|
} from '../LegendAI_LegendApplicationPlugin_Extension.js';
|
|
45
40
|
import type { QueryExplicitExecutionContextInfo } from '@finos/legend-graph';
|
|
@@ -47,32 +42,9 @@ import type { QueryExplicitExecutionContextInfo } from '@finos/legend-graph';
|
|
|
47
42
|
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
48
43
|
const MAX_THINKING_ERROR_PREVIEW_LENGTH = 200;
|
|
49
44
|
const DEFAULT_MAX_JUDGE_ATTEMPTS = 5;
|
|
50
|
-
const DEFAULT_MAX_EXECUTION_RETRIES = 3;
|
|
51
|
-
const ANALYSIS_TIMEOUT_MS = 15_000;
|
|
52
45
|
|
|
53
46
|
const SUGGESTED_QUERIES_DELIMITER = '---SUGGESTED_QUERIES---';
|
|
54
47
|
|
|
55
|
-
export function elapsedSeconds(startTime: number, decimals: 1 | 2 = 1): string {
|
|
56
|
-
return ((Date.now() - startTime) / 1000).toFixed(decimals);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function withTimeout<T>(
|
|
60
|
-
promise: Promise<T>,
|
|
61
|
-
ms: number,
|
|
62
|
-
): Promise<T | undefined> {
|
|
63
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
-
return Promise.race([
|
|
65
|
-
promise.finally(() => {
|
|
66
|
-
if (timer !== undefined) {
|
|
67
|
-
clearTimeout(timer);
|
|
68
|
-
}
|
|
69
|
-
}),
|
|
70
|
-
new Promise<undefined>((resolve) => {
|
|
71
|
-
timer = setTimeout(() => resolve(undefined), ms);
|
|
72
|
-
}),
|
|
73
|
-
]);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
48
|
function deduplicateColumns(columns: string[]): string[] {
|
|
77
49
|
const seen = new Map<string, number>();
|
|
78
50
|
return columns.map((col) => {
|
|
@@ -86,7 +58,7 @@ export type MessageSetter = React.Dispatch<
|
|
|
86
58
|
React.SetStateAction<LegendAIMessage[]>
|
|
87
59
|
>;
|
|
88
60
|
|
|
89
|
-
|
|
61
|
+
function createMessagePair(
|
|
90
62
|
text: string,
|
|
91
63
|
): [LegendAIUserMessage, LegendAIAssistantMessage] {
|
|
92
64
|
return [
|
|
@@ -97,22 +69,19 @@ export function createMessagePair(
|
|
|
97
69
|
thinkingSteps: [],
|
|
98
70
|
sql: null,
|
|
99
71
|
textAnswer: null,
|
|
100
|
-
dataContext: null,
|
|
101
72
|
gridData: null,
|
|
102
73
|
error: null,
|
|
103
|
-
errorType: null,
|
|
104
74
|
sqlGenTime: null,
|
|
105
75
|
execTime: null,
|
|
106
76
|
thinkingDuration: null,
|
|
107
77
|
isProcessing: true,
|
|
108
78
|
isExecuting: false,
|
|
109
79
|
suggestedQueries: [],
|
|
110
|
-
fallbackAction: null,
|
|
111
80
|
},
|
|
112
81
|
];
|
|
113
82
|
}
|
|
114
83
|
|
|
115
|
-
|
|
84
|
+
interface LegendAIOperationContext {
|
|
116
85
|
config: LegendAIConfig;
|
|
117
86
|
plugin: LegendAI_LegendApplicationPlugin_Extension;
|
|
118
87
|
history: LegendAIConversationTurn[];
|
|
@@ -150,7 +119,7 @@ export function addThinkingStep(
|
|
|
150
119
|
? { ...s, status: LegendAIThinkingStepStatus.DONE }
|
|
151
120
|
: s,
|
|
152
121
|
),
|
|
153
|
-
{
|
|
122
|
+
{ label, status: LegendAIThinkingStepStatus.ACTIVE },
|
|
154
123
|
],
|
|
155
124
|
}));
|
|
156
125
|
}
|
|
@@ -165,18 +134,10 @@ export function completeThinkingSteps(setMessages: MessageSetter): void {
|
|
|
165
134
|
}));
|
|
166
135
|
}
|
|
167
136
|
|
|
168
|
-
export function classifyError(error: Error): LegendAIErrorType {
|
|
169
|
-
if (error instanceof LegendAIServiceError) {
|
|
170
|
-
return error.errorType;
|
|
171
|
-
}
|
|
172
|
-
return LegendAIErrorType.GENERAL;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
137
|
export function finishWithThinkingError(
|
|
176
138
|
setMessages: MessageSetter,
|
|
177
139
|
errorMsg: string,
|
|
178
140
|
startTime: number,
|
|
179
|
-
errorType?: LegendAIErrorType,
|
|
180
141
|
): void {
|
|
181
142
|
updateLastAssistant(setMessages, (msg) => ({
|
|
182
143
|
thinkingSteps: msg.thinkingSteps.map((s) =>
|
|
@@ -185,9 +146,8 @@ export function finishWithThinkingError(
|
|
|
185
146
|
: s,
|
|
186
147
|
),
|
|
187
148
|
error: errorMsg.slice(0, MAX_ERROR_MESSAGE_LENGTH),
|
|
188
|
-
errorType: errorType ?? null,
|
|
189
149
|
isProcessing: false,
|
|
190
|
-
thinkingDuration:
|
|
150
|
+
thinkingDuration: ((Date.now() - startTime) / 1000).toFixed(1),
|
|
191
151
|
}));
|
|
192
152
|
}
|
|
193
153
|
|
|
@@ -346,7 +306,7 @@ export async function handleMetadataQuestion(
|
|
|
346
306
|
textAnswer: answer,
|
|
347
307
|
suggestedQueries,
|
|
348
308
|
isProcessing: false,
|
|
349
|
-
thinkingDuration:
|
|
309
|
+
thinkingDuration: ((Date.now() - startTime) / 1000).toFixed(1),
|
|
350
310
|
}));
|
|
351
311
|
}
|
|
352
312
|
|
|
@@ -380,7 +340,6 @@ export async function generateAndJudgeSql(
|
|
|
380
340
|
setMessages,
|
|
381
341
|
buildGenerationFailureMessage(failure, suggestion, services),
|
|
382
342
|
startTime,
|
|
383
|
-
LegendAIErrorType.GENERATION,
|
|
384
343
|
);
|
|
385
344
|
return null;
|
|
386
345
|
}
|
|
@@ -391,7 +350,6 @@ export async function generateAndJudgeSql(
|
|
|
391
350
|
setMessages,
|
|
392
351
|
'Could not extract SQL from LLM response.\nTry rephrasing your question or ask about a specific service.',
|
|
393
352
|
startTime,
|
|
394
|
-
LegendAIErrorType.GENERATION,
|
|
395
353
|
);
|
|
396
354
|
return null;
|
|
397
355
|
}
|
|
@@ -461,10 +419,10 @@ function reportExecutionResult(
|
|
|
461
419
|
|
|
462
420
|
updateLastAssistant(setMessages, () => ({
|
|
463
421
|
gridData: { columnDefs: buildColumnDefsFromNames(columns), rowData: rows },
|
|
464
|
-
execTime:
|
|
422
|
+
execTime: ((Date.now() - execStartTime) / 1000).toFixed(2),
|
|
465
423
|
isProcessing: false,
|
|
466
424
|
isExecuting: false,
|
|
467
|
-
thinkingDuration:
|
|
425
|
+
thinkingDuration: ((Date.now() - startTime) / 1000).toFixed(1),
|
|
468
426
|
}));
|
|
469
427
|
return { columns, rows };
|
|
470
428
|
}
|
|
@@ -495,7 +453,6 @@ export async function executeSqlAndReport(
|
|
|
495
453
|
);
|
|
496
454
|
} catch (executeError) {
|
|
497
455
|
assertErrorThrown(executeError);
|
|
498
|
-
const execErrorType = classifyError(executeError);
|
|
499
456
|
addThinkingStep(
|
|
500
457
|
setMessages,
|
|
501
458
|
`Execution failed: ${executeError.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
@@ -504,12 +461,9 @@ export async function executeSqlAndReport(
|
|
|
504
461
|
setMessages,
|
|
505
462
|
buildExecutionErrorMessage(executeError.message, services),
|
|
506
463
|
startTime,
|
|
507
|
-
execErrorType === LegendAIErrorType.GENERAL
|
|
508
|
-
? LegendAIErrorType.EXECUTION
|
|
509
|
-
: execErrorType,
|
|
510
464
|
);
|
|
511
465
|
updateLastAssistant(setMessages, () => ({
|
|
512
|
-
execTime:
|
|
466
|
+
execTime: ((Date.now() - execStartTime) / 1000).toFixed(2),
|
|
513
467
|
isExecuting: false,
|
|
514
468
|
}));
|
|
515
469
|
return undefined;
|
|
@@ -542,183 +496,44 @@ export async function executePureQueryAndReport(
|
|
|
542
496
|
);
|
|
543
497
|
} catch (executeError) {
|
|
544
498
|
assertErrorThrown(executeError);
|
|
545
|
-
const execErrorType = classifyError(executeError);
|
|
546
499
|
addThinkingStep(
|
|
547
500
|
setMessages,
|
|
548
501
|
`Execution failed: ${executeError.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
549
502
|
);
|
|
550
503
|
completeThinkingSteps(setMessages);
|
|
551
504
|
updateLastAssistant(setMessages, () => ({
|
|
552
|
-
execTime:
|
|
505
|
+
execTime: ((Date.now() - execStartTime) / 1000).toFixed(2),
|
|
553
506
|
isExecuting: false,
|
|
554
507
|
isProcessing: false,
|
|
555
508
|
error: `Execution failed: ${executeError.message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}`,
|
|
556
|
-
|
|
557
|
-
execErrorType === LegendAIErrorType.GENERAL
|
|
558
|
-
? LegendAIErrorType.EXECUTION
|
|
559
|
-
: execErrorType,
|
|
560
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
509
|
+
thinkingDuration: ((Date.now() - startTime) / 1000).toFixed(1),
|
|
561
510
|
}));
|
|
562
511
|
return { columns: [], rows: [] };
|
|
563
512
|
}
|
|
564
513
|
}
|
|
565
514
|
|
|
566
|
-
export async function analyzeOrchestratorResults(
|
|
567
|
-
question: string,
|
|
568
|
-
query: string,
|
|
569
|
-
execResult: LegendAISqlExecutionResultData,
|
|
570
|
-
metadata: LegendAIProductMetadata,
|
|
571
|
-
context: LegendAIOperationContext,
|
|
572
|
-
startTime: number,
|
|
573
|
-
): Promise<void> {
|
|
574
|
-
const { config, plugin, setMessages } = context;
|
|
575
|
-
addThinkingStep(setMessages, 'Analyzing results...');
|
|
576
|
-
updateLastAssistant(setMessages, () => ({
|
|
577
|
-
isProcessing: true,
|
|
578
|
-
}));
|
|
579
|
-
const analysis = await withTimeout(
|
|
580
|
-
plugin.analyzeQueryResults(
|
|
581
|
-
question,
|
|
582
|
-
query,
|
|
583
|
-
execResult.columns,
|
|
584
|
-
execResult.rows,
|
|
585
|
-
metadata,
|
|
586
|
-
config,
|
|
587
|
-
),
|
|
588
|
-
ANALYSIS_TIMEOUT_MS,
|
|
589
|
-
);
|
|
590
|
-
if (analysis) {
|
|
591
|
-
completeThinkingSteps(setMessages);
|
|
592
|
-
updateLastAssistant(setMessages, () => ({
|
|
593
|
-
textAnswer: analysis.summary,
|
|
594
|
-
suggestedQueries: analysis.suggestedQueries,
|
|
595
|
-
isProcessing: false,
|
|
596
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
597
|
-
}));
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
async function handleEmptyOrchestratorResults(
|
|
602
|
-
question: string,
|
|
603
|
-
legendQuery: string,
|
|
604
|
-
orchestratorOptions: Required<LegendAIOrchestratorOptionsParam>,
|
|
605
|
-
metadata: LegendAIProductMetadata,
|
|
606
|
-
resolvedEntities: LegendAIResolvedEntities,
|
|
607
|
-
context: LegendAIOperationContext,
|
|
608
|
-
startTime: number,
|
|
609
|
-
): Promise<void> {
|
|
610
|
-
const { dataProductCoordinates, pureExecutionContext } = orchestratorOptions;
|
|
611
|
-
const { config, plugin, setMessages } = context;
|
|
612
|
-
|
|
613
|
-
if (resolvedEntities.relatedEntities.length > 0) {
|
|
614
|
-
const alternateRoot = resolvedEntities.relatedEntities[0];
|
|
615
|
-
if (alternateRoot) {
|
|
616
|
-
addThinkingStep(
|
|
617
|
-
setMessages,
|
|
618
|
-
`No results with ${resolvedEntities.rootEntity.split('::').pop() ?? resolvedEntities.rootEntity}, retrying with ${alternateRoot.split('::').pop() ?? alternateRoot}...`,
|
|
619
|
-
);
|
|
620
|
-
|
|
621
|
-
try {
|
|
622
|
-
const retryResponse = await plugin.generateQueryViaOrchestrator(
|
|
623
|
-
{
|
|
624
|
-
user_question: question,
|
|
625
|
-
semantic_search_resolution_details: {
|
|
626
|
-
data_product_coordinates: dataProductCoordinates,
|
|
627
|
-
root_entity: alternateRoot,
|
|
628
|
-
related_entities: resolvedEntities.relatedEntities.slice(1),
|
|
629
|
-
},
|
|
630
|
-
},
|
|
631
|
-
config,
|
|
632
|
-
);
|
|
633
|
-
|
|
634
|
-
updateLastAssistant(setMessages, () => ({
|
|
635
|
-
sql: retryResponse.legend_query,
|
|
636
|
-
sqlGenTime: elapsedSeconds(startTime, 2),
|
|
637
|
-
isExecuting: true,
|
|
638
|
-
}));
|
|
639
|
-
|
|
640
|
-
const retryResult = await executePureQueryAndReport(
|
|
641
|
-
retryResponse.legend_query,
|
|
642
|
-
pureExecutionContext,
|
|
643
|
-
dataProductCoordinates,
|
|
644
|
-
config,
|
|
645
|
-
plugin,
|
|
646
|
-
setMessages,
|
|
647
|
-
startTime,
|
|
648
|
-
);
|
|
649
|
-
|
|
650
|
-
if (retryResult.rows.length > 0) {
|
|
651
|
-
await analyzeOrchestratorResults(
|
|
652
|
-
question,
|
|
653
|
-
retryResponse.legend_query,
|
|
654
|
-
retryResult,
|
|
655
|
-
metadata,
|
|
656
|
-
context,
|
|
657
|
-
startTime,
|
|
658
|
-
);
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
} catch {
|
|
662
|
-
/* empty */
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
addThinkingStep(
|
|
668
|
-
setMessages,
|
|
669
|
-
'No results returned \u2014 building contextual guidance...',
|
|
670
|
-
);
|
|
671
|
-
updateLastAssistant(setMessages, () => ({
|
|
672
|
-
isProcessing: true,
|
|
673
|
-
}));
|
|
674
|
-
const fallback = await withTimeout(
|
|
675
|
-
plugin.buildNoResultsFallback(question, legendQuery, metadata, config),
|
|
676
|
-
ANALYSIS_TIMEOUT_MS,
|
|
677
|
-
);
|
|
678
|
-
if (fallback) {
|
|
679
|
-
completeThinkingSteps(setMessages);
|
|
680
|
-
updateLastAssistant(setMessages, () => ({
|
|
681
|
-
textAnswer: fallback.summary,
|
|
682
|
-
suggestedQueries: fallback.suggestedQueries,
|
|
683
|
-
isProcessing: false,
|
|
684
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
685
|
-
}));
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
515
|
export async function processQuestionViaOrchestrator(
|
|
690
516
|
question: string,
|
|
691
517
|
dataProductCoordinates: LegendAIOrchestratorDataProductCoordinates,
|
|
692
|
-
|
|
518
|
+
_metadata: LegendAIProductMetadata,
|
|
693
519
|
context: LegendAIOperationContext,
|
|
694
520
|
pureExecutionContext?: QueryExplicitExecutionContextInfo,
|
|
695
|
-
preResolvedEntities?: LegendAIResolvedEntities,
|
|
696
521
|
): Promise<void> {
|
|
697
522
|
const { config, plugin, setMessages } = context;
|
|
698
523
|
const startTime = Date.now();
|
|
699
524
|
|
|
700
525
|
try {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
);
|
|
708
|
-
} else {
|
|
709
|
-
addThinkingStep(setMessages, 'Resolving entities for your query...');
|
|
710
|
-
resolvedEntities = await plugin.resolveEntitiesForQuery(
|
|
711
|
-
question,
|
|
712
|
-
dataProductCoordinates,
|
|
713
|
-
config,
|
|
714
|
-
pureExecutionContext,
|
|
715
|
-
);
|
|
716
|
-
addThinkingStep(
|
|
717
|
-
setMessages,
|
|
718
|
-
`Found root entity: ${resolvedEntities.rootEntity.split('::').pop() ?? resolvedEntities.rootEntity}`,
|
|
719
|
-
);
|
|
720
|
-
}
|
|
526
|
+
addThinkingStep(setMessages, 'Resolving entities for your query...');
|
|
527
|
+
const resolvedEntities = await plugin.resolveEntitiesForQuery(
|
|
528
|
+
question,
|
|
529
|
+
dataProductCoordinates,
|
|
530
|
+
config,
|
|
531
|
+
);
|
|
721
532
|
|
|
533
|
+
addThinkingStep(
|
|
534
|
+
setMessages,
|
|
535
|
+
`Found root entity: ${resolvedEntities.rootEntity.split('::').pop() ?? resolvedEntities.rootEntity}`,
|
|
536
|
+
);
|
|
722
537
|
if (resolvedEntities.relatedEntities.length > 0) {
|
|
723
538
|
addThinkingStep(
|
|
724
539
|
setMessages,
|
|
@@ -739,7 +554,7 @@ export async function processQuestionViaOrchestrator(
|
|
|
739
554
|
config,
|
|
740
555
|
);
|
|
741
556
|
|
|
742
|
-
const queryGenTime =
|
|
557
|
+
const queryGenTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
743
558
|
completeThinkingSteps(setMessages);
|
|
744
559
|
updateLastAssistant(setMessages, () => ({
|
|
745
560
|
sql: orchestratorResponse.legend_query,
|
|
@@ -754,13 +569,12 @@ export async function processQuestionViaOrchestrator(
|
|
|
754
569
|
isExecuting: false,
|
|
755
570
|
error:
|
|
756
571
|
'No execution context available — cannot execute query via engine.',
|
|
757
|
-
|
|
758
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
572
|
+
thinkingDuration: ((Date.now() - startTime) / 1000).toFixed(1),
|
|
759
573
|
}));
|
|
760
574
|
return;
|
|
761
575
|
}
|
|
762
576
|
|
|
763
|
-
|
|
577
|
+
await executePureQueryAndReport(
|
|
764
578
|
orchestratorResponse.legend_query,
|
|
765
579
|
pureExecutionContext,
|
|
766
580
|
dataProductCoordinates,
|
|
@@ -769,633 +583,82 @@ export async function processQuestionViaOrchestrator(
|
|
|
769
583
|
setMessages,
|
|
770
584
|
startTime,
|
|
771
585
|
);
|
|
772
|
-
|
|
773
|
-
try {
|
|
774
|
-
if (execResult.rows.length > 0) {
|
|
775
|
-
await analyzeOrchestratorResults(
|
|
776
|
-
question,
|
|
777
|
-
orchestratorResponse.legend_query,
|
|
778
|
-
execResult,
|
|
779
|
-
metadata,
|
|
780
|
-
context,
|
|
781
|
-
startTime,
|
|
782
|
-
);
|
|
783
|
-
} else {
|
|
784
|
-
await handleEmptyOrchestratorResults(
|
|
785
|
-
question,
|
|
786
|
-
orchestratorResponse.legend_query,
|
|
787
|
-
{ dataProductCoordinates, pureExecutionContext },
|
|
788
|
-
metadata,
|
|
789
|
-
resolvedEntities,
|
|
790
|
-
context,
|
|
791
|
-
startTime,
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
} catch {
|
|
795
|
-
/* empty */
|
|
796
|
-
} finally {
|
|
797
|
-
completeThinkingSteps(setMessages);
|
|
798
|
-
updateLastAssistant(setMessages, () => ({
|
|
799
|
-
isProcessing: false,
|
|
800
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
801
|
-
}));
|
|
802
|
-
}
|
|
803
586
|
} catch (error) {
|
|
804
587
|
assertErrorThrown(error);
|
|
805
|
-
const orchErrorType = classifyError(error);
|
|
806
588
|
addThinkingStep(
|
|
807
589
|
setMessages,
|
|
808
590
|
`Error: ${error.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
809
591
|
);
|
|
810
|
-
|
|
811
|
-
try {
|
|
812
|
-
addThinkingStep(
|
|
813
|
-
setMessages,
|
|
814
|
-
'Building guidance from available metadata...',
|
|
815
|
-
);
|
|
816
|
-
const fallbackText = await withTimeout(
|
|
817
|
-
plugin.buildFailureFallback(question, error.message, metadata, config),
|
|
818
|
-
ANALYSIS_TIMEOUT_MS,
|
|
819
|
-
);
|
|
820
|
-
if (fallbackText) {
|
|
821
|
-
completeThinkingSteps(setMessages);
|
|
822
|
-
updateLastAssistant(setMessages, () => ({
|
|
823
|
-
dataContext: fallbackText,
|
|
824
|
-
isProcessing: false,
|
|
825
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
826
|
-
}));
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
} catch {
|
|
830
|
-
/* empty */
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
finishWithThinkingError(
|
|
834
|
-
setMessages,
|
|
835
|
-
error.message,
|
|
836
|
-
startTime,
|
|
837
|
-
orchErrorType,
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function cleanLlmSqlResponse(raw: string): string {
|
|
843
|
-
return raw
|
|
844
|
-
.trim()
|
|
845
|
-
.replace(/^```\w*\n?/, '')
|
|
846
|
-
.replace(/\n?```$/, '')
|
|
847
|
-
.replace(/;\s*$/, '')
|
|
848
|
-
.trim();
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
function isValidSqlCorrection(trimmed: string, currentSql: string): boolean {
|
|
852
|
-
return (
|
|
853
|
-
trimmed.length > 0 &&
|
|
854
|
-
trimmed.toLowerCase().startsWith('select') &&
|
|
855
|
-
trimmed !== currentSql
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const ALIAS_DOT_COL_PATTERN = /\b(?<tbl>[a-z]\w*)\s*\.\s*"(?<col>[^"]+)"/gi;
|
|
860
|
-
|
|
861
|
-
const JOIN_PATTERN = /\bJOIN\b/i;
|
|
862
|
-
|
|
863
|
-
const ORDER_BY_SPLIT = /\bORDER\s+BY\b/i;
|
|
864
|
-
|
|
865
|
-
export function sanitizeJoinOrderBy(sql: string): string {
|
|
866
|
-
if (!JOIN_PATTERN.test(sql)) {
|
|
867
|
-
return sql;
|
|
868
|
-
}
|
|
869
|
-
const parts = sql.split(ORDER_BY_SPLIT);
|
|
870
|
-
if (parts.length < 2) {
|
|
871
|
-
return sql;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const beforeOrderBy = parts[0] ?? '';
|
|
875
|
-
const afterOrderBy = parts.slice(1).join('ORDER BY').replace(/^\s+/, '');
|
|
876
|
-
|
|
877
|
-
const selectAliases = new Map<string, string>();
|
|
878
|
-
const aliasRegex =
|
|
879
|
-
/\b(?<tbl>[a-z]\w*)\s*\.\s*"(?<col>[^"]+)"\s+AS\s+(?:"(?<qAlias>[^"]+)"|(?<uAlias>\w+))/gi;
|
|
880
|
-
let m: RegExpExecArray | null;
|
|
881
|
-
while ((m = aliasRegex.exec(beforeOrderBy)) !== null) {
|
|
882
|
-
const tableAlias = (m.groups?.tbl ?? '').toLowerCase();
|
|
883
|
-
const colName = (m.groups?.col ?? '').toLowerCase();
|
|
884
|
-
const asAlias = m.groups?.qAlias ?? m.groups?.uAlias ?? '';
|
|
885
|
-
selectAliases.set(`${tableAlias}.${colName}`, asAlias);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (selectAliases.size === 0) {
|
|
889
|
-
return sql;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
const rewritten = afterOrderBy.replaceAll(
|
|
893
|
-
ALIAS_DOT_COL_PATTERN,
|
|
894
|
-
(...args) => {
|
|
895
|
-
const groups = args[args.length - 1] as {
|
|
896
|
-
tbl: string;
|
|
897
|
-
col: string;
|
|
898
|
-
};
|
|
899
|
-
const key = `${groups.tbl.toLowerCase()}.${groups.col.toLowerCase()}`;
|
|
900
|
-
const alias = selectAliases.get(key);
|
|
901
|
-
return alias ? `"${alias}"` : String(args[0]);
|
|
902
|
-
},
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
if (rewritten === afterOrderBy) {
|
|
906
|
-
return sql;
|
|
907
|
-
}
|
|
908
|
-
return `${beforeOrderBy}ORDER BY ${rewritten}`;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
const UNION_ALL_PATTERN = /\bUNION\s+ALL\b/i;
|
|
912
|
-
|
|
913
|
-
const LITERAL_COL_PATTERN = /,\s*'[^']*'\s+AS\s+(?:"[^"]+"|[a-z]\w*)/gi;
|
|
914
|
-
|
|
915
|
-
export function sanitizeLiteralColumns(sql: string): string {
|
|
916
|
-
if (!UNION_ALL_PATTERN.test(sql)) {
|
|
917
|
-
return sql;
|
|
918
|
-
}
|
|
919
|
-
LITERAL_COL_PATTERN.lastIndex = 0;
|
|
920
|
-
if (!LITERAL_COL_PATTERN.test(sql)) {
|
|
921
|
-
return sql;
|
|
922
|
-
}
|
|
923
|
-
LITERAL_COL_PATTERN.lastIndex = 0;
|
|
924
|
-
return sql.replace(LITERAL_COL_PATTERN, '');
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
const SERVICE_PARAM_DATE_LIKE =
|
|
928
|
-
/date|time|day|month|year|period|asOf|businessDate|processingDate|snapshot/i;
|
|
929
|
-
|
|
930
|
-
function hasUnresolvableParams(service: TDSServiceSchema): boolean {
|
|
931
|
-
return service.parameters.some((p) => !SERVICE_PARAM_DATE_LIKE.test(p));
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
function getNonDateParamNames(service: TDSServiceSchema): string[] {
|
|
935
|
-
return service.parameters.filter((p) => !SERVICE_PARAM_DATE_LIKE.test(p));
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
export function stripNonDateServiceParams(sql: string): string {
|
|
939
|
-
return sql.replaceAll(/,\s*\w+\s*=>\s*'[^']*'/g, (match) => {
|
|
940
|
-
const paramName = /,\s*(?<param>\w+)\s*=>/.exec(match)?.groups?.param;
|
|
941
|
-
if (!paramName) {
|
|
942
|
-
return match;
|
|
943
|
-
}
|
|
944
|
-
if (
|
|
945
|
-
paramName === 'coordinates' ||
|
|
946
|
-
SERVICE_PARAM_DATE_LIKE.test(paramName)
|
|
947
|
-
) {
|
|
948
|
-
return match;
|
|
949
|
-
}
|
|
950
|
-
return '';
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
async function executeSqlForServices(
|
|
955
|
-
sql: string,
|
|
956
|
-
services: TDSServiceSchema[],
|
|
957
|
-
dataProductCoordinates:
|
|
958
|
-
| LegendAIOrchestratorDataProductCoordinates
|
|
959
|
-
| undefined,
|
|
960
|
-
plugin: LegendAI_LegendApplicationPlugin_Extension,
|
|
961
|
-
config: LegendAIConfig,
|
|
962
|
-
): Promise<LegendAISqlExecutionResultData> {
|
|
963
|
-
const safeSql = sanitizeLiteralColumns(sanitizeJoinOrderBy(sql));
|
|
964
|
-
const isAccessPoint = services.some(
|
|
965
|
-
(s) => s.sourceType === TDSServiceSourceType.ACCESS_POINT,
|
|
966
|
-
);
|
|
967
|
-
if (isAccessPoint && dataProductCoordinates) {
|
|
968
|
-
return plugin.executeLakehouseSql(safeSql, dataProductCoordinates, config);
|
|
969
|
-
}
|
|
970
|
-
return plugin.executeSql(safeSql, config);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
interface SqlExecutionOutcome {
|
|
974
|
-
sql: string;
|
|
975
|
-
result?: LegendAISqlExecutionResultData;
|
|
976
|
-
error?: string;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
async function executeSqlWithRetries(
|
|
980
|
-
initialSql: string,
|
|
981
|
-
question: string,
|
|
982
|
-
services: TDSServiceSchema[],
|
|
983
|
-
coordinates: string,
|
|
984
|
-
dataProductCoordinates:
|
|
985
|
-
| LegendAIOrchestratorDataProductCoordinates
|
|
986
|
-
| undefined,
|
|
987
|
-
context: LegendAIOperationContext,
|
|
988
|
-
): Promise<SqlExecutionOutcome> {
|
|
989
|
-
const { plugin, config, setMessages } = context;
|
|
990
|
-
let currentSql = initialSql;
|
|
991
|
-
|
|
992
|
-
for (let attempt = 0; attempt <= DEFAULT_MAX_EXECUTION_RETRIES; attempt++) {
|
|
993
|
-
try {
|
|
994
|
-
const result = await executeSqlForServices(
|
|
995
|
-
currentSql,
|
|
996
|
-
services,
|
|
997
|
-
dataProductCoordinates,
|
|
998
|
-
plugin,
|
|
999
|
-
config,
|
|
1000
|
-
);
|
|
1001
|
-
return { sql: currentSql, result };
|
|
1002
|
-
} catch (executeError) {
|
|
1003
|
-
assertErrorThrown(executeError);
|
|
1004
|
-
if (attempt >= DEFAULT_MAX_EXECUTION_RETRIES) {
|
|
1005
|
-
return { sql: currentSql, error: executeError.message };
|
|
1006
|
-
}
|
|
1007
|
-
addThinkingStep(
|
|
1008
|
-
setMessages,
|
|
1009
|
-
`Execution failed (attempt ${attempt + 1}/${DEFAULT_MAX_EXECUTION_RETRIES + 1}), correcting query...`,
|
|
1010
|
-
);
|
|
1011
|
-
const corrected = await attemptErrorCorrection(
|
|
1012
|
-
currentSql,
|
|
1013
|
-
executeError.message,
|
|
1014
|
-
question,
|
|
1015
|
-
services,
|
|
1016
|
-
coordinates,
|
|
1017
|
-
plugin,
|
|
1018
|
-
config,
|
|
1019
|
-
);
|
|
1020
|
-
if (corrected) {
|
|
1021
|
-
currentSql = corrected;
|
|
1022
|
-
updateLastAssistant(setMessages, () => ({ sql: currentSql }));
|
|
1023
|
-
continue;
|
|
1024
|
-
}
|
|
1025
|
-
return { sql: currentSql, error: executeError.message };
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return { sql: currentSql };
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
async function attemptErrorCorrection(
|
|
1032
|
-
currentSql: string,
|
|
1033
|
-
errorMessage: string,
|
|
1034
|
-
question: string,
|
|
1035
|
-
services: TDSServiceSchema[],
|
|
1036
|
-
coordinates: string,
|
|
1037
|
-
plugin: LegendAI_LegendApplicationPlugin_Extension,
|
|
1038
|
-
config: LegendAIConfig,
|
|
1039
|
-
): Promise<string | undefined> {
|
|
1040
|
-
const prompt = plugin.buildErrorCorrectionPrompt(
|
|
1041
|
-
currentSql,
|
|
1042
|
-
errorMessage,
|
|
1043
|
-
question,
|
|
1044
|
-
services,
|
|
1045
|
-
coordinates,
|
|
1046
|
-
);
|
|
1047
|
-
if (!prompt) {
|
|
1048
|
-
return undefined;
|
|
1049
|
-
}
|
|
1050
|
-
try {
|
|
1051
|
-
const correctedSql = await plugin.callLLM(prompt, config);
|
|
1052
|
-
const trimmed = cleanLlmSqlResponse(correctedSql);
|
|
1053
|
-
if (isValidSqlCorrection(trimmed, currentSql)) {
|
|
1054
|
-
return trimmed;
|
|
1055
|
-
}
|
|
1056
|
-
} catch {
|
|
1057
|
-
/* empty */
|
|
592
|
+
finishWithThinkingError(setMessages, error.message, startTime);
|
|
1058
593
|
}
|
|
1059
|
-
return undefined;
|
|
1060
594
|
}
|
|
1061
595
|
|
|
1062
|
-
async function
|
|
1063
|
-
currentSql: string,
|
|
596
|
+
async function processDataQuery(
|
|
1064
597
|
question: string,
|
|
1065
598
|
services: TDSServiceSchema[],
|
|
1066
599
|
coordinates: string,
|
|
1067
|
-
dataProductCoordinates:
|
|
1068
|
-
| LegendAIOrchestratorDataProductCoordinates
|
|
1069
|
-
| undefined,
|
|
1070
|
-
context: LegendAIOperationContext,
|
|
1071
|
-
): Promise<
|
|
1072
|
-
{ sql: string; result: LegendAISqlExecutionResultData } | undefined
|
|
1073
|
-
> {
|
|
1074
|
-
const { plugin, config, setMessages } = context;
|
|
1075
|
-
addThinkingStep(
|
|
1076
|
-
setMessages,
|
|
1077
|
-
'Query returned 0 rows, attempting filter correction...',
|
|
1078
|
-
);
|
|
1079
|
-
const prompt = plugin.buildZeroRowCorrectionPrompt(
|
|
1080
|
-
currentSql,
|
|
1081
|
-
question,
|
|
1082
|
-
services,
|
|
1083
|
-
coordinates,
|
|
1084
|
-
);
|
|
1085
|
-
if (!prompt) {
|
|
1086
|
-
return undefined;
|
|
1087
|
-
}
|
|
1088
|
-
try {
|
|
1089
|
-
const correctedSql = await plugin.callLLM(prompt, config);
|
|
1090
|
-
const trimmed = cleanLlmSqlResponse(correctedSql);
|
|
1091
|
-
if (!isValidSqlCorrection(trimmed, currentSql)) {
|
|
1092
|
-
return undefined;
|
|
1093
|
-
}
|
|
1094
|
-
addThinkingStep(setMessages, 'Retrying with corrected filters...');
|
|
1095
|
-
updateLastAssistant(setMessages, () => ({ sql: trimmed }));
|
|
1096
|
-
try {
|
|
1097
|
-
const retryResult = await executeSqlForServices(
|
|
1098
|
-
trimmed,
|
|
1099
|
-
services,
|
|
1100
|
-
dataProductCoordinates,
|
|
1101
|
-
plugin,
|
|
1102
|
-
config,
|
|
1103
|
-
);
|
|
1104
|
-
if (retryResult.rows.length > 0) {
|
|
1105
|
-
return { sql: trimmed, result: retryResult };
|
|
1106
|
-
}
|
|
1107
|
-
} catch {
|
|
1108
|
-
/* empty */
|
|
1109
|
-
}
|
|
1110
|
-
} catch {
|
|
1111
|
-
/* empty */
|
|
1112
|
-
}
|
|
1113
|
-
return undefined;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
function buildZeroRowMessage(services: TDSServiceSchema[]): string {
|
|
1117
|
-
const withUnresolvable = services.filter((s) => hasUnresolvableParams(s));
|
|
1118
|
-
if (withUnresolvable.length > 0) {
|
|
1119
|
-
const parts: string[] = [];
|
|
1120
|
-
for (const svc of withUnresolvable) {
|
|
1121
|
-
for (const paramName of getNonDateParamNames(svc)) {
|
|
1122
|
-
const matchingCol = svc.columns.find((c) => c.name === paramName);
|
|
1123
|
-
const docHint = matchingCol?.documentation ?? matchingCol?.sampleValues;
|
|
1124
|
-
if (docHint) {
|
|
1125
|
-
parts.push(`**${paramName}** (${docHint})`);
|
|
1126
|
-
} else {
|
|
1127
|
-
parts.push(`**${paramName}**`);
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
const uniqueParts = [...new Set(parts)];
|
|
1132
|
-
const firstSvc = withUnresolvable[0];
|
|
1133
|
-
const firstParam = firstSvc
|
|
1134
|
-
? (getNonDateParamNames(firstSvc)[0] ?? 'parameter')
|
|
1135
|
-
: 'parameter';
|
|
1136
|
-
return `The SQL query executed successfully but returned **0 rows**. This service requires specific values for ${uniqueParts.join(', ')} to return data. Please include ${uniqueParts.length === 1 ? 'a value' : 'values'} in your question, e.g., "show data where ${firstParam} is [your value]".`;
|
|
1137
|
-
}
|
|
1138
|
-
return 'The SQL query executed successfully but returned **0 rows**. The applied filters may not match any records, or the specific values may not exist in the queried datasets.';
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
function offerOrchestratorFallbackMessage(
|
|
1142
|
-
setMessages: MessageSetter,
|
|
1143
|
-
startTime: number,
|
|
1144
|
-
fallbackMessage: string,
|
|
1145
|
-
): void {
|
|
1146
|
-
updateLastAssistant(setMessages, () => ({
|
|
1147
|
-
textAnswer: fallbackMessage,
|
|
1148
|
-
fallbackAction: {
|
|
1149
|
-
label: 'Try Legend AI Orchestrator',
|
|
1150
|
-
actionId: LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID,
|
|
1151
|
-
},
|
|
1152
|
-
isProcessing: false,
|
|
1153
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
1154
|
-
}));
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
function reportFatalQueryError(
|
|
1158
|
-
setMessages: MessageSetter,
|
|
1159
|
-
startTime: number,
|
|
1160
|
-
errorMessage: string,
|
|
1161
|
-
errorType: LegendAIErrorType,
|
|
1162
|
-
): void {
|
|
1163
|
-
finishWithThinkingError(setMessages, errorMessage, startTime, errorType);
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function handleSqlGenerationFailure(
|
|
1167
|
-
setMessages: MessageSetter,
|
|
1168
|
-
startTime: number,
|
|
1169
|
-
hasOrchestratorFallback: boolean,
|
|
1170
|
-
orchestratorMessage: string,
|
|
1171
|
-
errorMessage: string,
|
|
1172
|
-
errorType: LegendAIErrorType,
|
|
1173
|
-
): void {
|
|
1174
|
-
completeThinkingSteps(setMessages);
|
|
1175
|
-
if (hasOrchestratorFallback) {
|
|
1176
|
-
offerOrchestratorFallbackMessage(
|
|
1177
|
-
setMessages,
|
|
1178
|
-
startTime,
|
|
1179
|
-
orchestratorMessage,
|
|
1180
|
-
);
|
|
1181
|
-
} else {
|
|
1182
|
-
reportFatalQueryError(setMessages, startTime, errorMessage, errorType);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
interface QueryResultReport {
|
|
1187
|
-
currentSql: string;
|
|
1188
|
-
sqlResult: LegendAISqlExecutionResultData;
|
|
1189
|
-
question: string;
|
|
1190
|
-
services: TDSServiceSchema[];
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
async function reportQueryResults(
|
|
1194
|
-
report: QueryResultReport,
|
|
1195
600
|
metadata: LegendAIProductMetadata,
|
|
1196
601
|
context: LegendAIOperationContext,
|
|
1197
602
|
startTime: number,
|
|
1198
|
-
|
|
603
|
+
orchestratorOptions?: LegendAIOrchestratorOptionsParam,
|
|
1199
604
|
): Promise<void> {
|
|
1200
|
-
const {
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
const columns = deduplicateColumns(sqlResult.columns);
|
|
1204
|
-
const rows = sqlResult.rows;
|
|
1205
|
-
completeThinkingSteps(setMessages);
|
|
1206
|
-
addThinkingStep(
|
|
1207
|
-
setMessages,
|
|
1208
|
-
`Retrieved ${rows.length} row${rows.length === 1 ? '' : 's'}`,
|
|
1209
|
-
);
|
|
1210
|
-
completeThinkingSteps(setMessages);
|
|
1211
|
-
updateLastAssistant(setMessages, () => ({
|
|
1212
|
-
sql: currentSql,
|
|
1213
|
-
gridData: {
|
|
1214
|
-
columnDefs: buildColumnDefsFromNames(columns),
|
|
1215
|
-
rowData: rows,
|
|
1216
|
-
},
|
|
1217
|
-
execTime: elapsedSeconds(startTime, 2),
|
|
1218
|
-
isProcessing: true,
|
|
1219
|
-
isExecuting: false,
|
|
1220
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
1221
|
-
}));
|
|
605
|
+
const { config, plugin, setMessages } = context;
|
|
606
|
+
const dataProductCoordinates = orchestratorOptions?.dataProductCoordinates;
|
|
607
|
+
const pureExecutionContext = orchestratorOptions?.pureExecutionContext;
|
|
1222
608
|
|
|
1223
|
-
|
|
1224
|
-
|
|
609
|
+
if (services.length === 0) {
|
|
610
|
+
if (config.orchestratorUrl && dataProductCoordinates) {
|
|
611
|
+
completeThinkingSteps(setMessages);
|
|
612
|
+
await processQuestionViaOrchestrator(
|
|
1225
613
|
question,
|
|
1226
|
-
|
|
1227
|
-
sqlResult,
|
|
614
|
+
dataProductCoordinates,
|
|
1228
615
|
metadata,
|
|
1229
616
|
context,
|
|
1230
|
-
|
|
1231
|
-
);
|
|
1232
|
-
} catch {
|
|
1233
|
-
/* empty */
|
|
1234
|
-
} finally {
|
|
1235
|
-
completeThinkingSteps(setMessages);
|
|
1236
|
-
updateLastAssistant(setMessages, () => ({
|
|
1237
|
-
isProcessing: false,
|
|
1238
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
1239
|
-
}));
|
|
1240
|
-
}
|
|
1241
|
-
} else {
|
|
1242
|
-
addThinkingStep(
|
|
1243
|
-
setMessages,
|
|
1244
|
-
'Query returned 0 rows after correction attempts.',
|
|
1245
|
-
);
|
|
1246
|
-
completeThinkingSteps(setMessages);
|
|
1247
|
-
const fallback = hasOrchestratorFallback
|
|
1248
|
-
? {
|
|
1249
|
-
fallbackAction: {
|
|
1250
|
-
label: 'Try Legend AI Orchestrator',
|
|
1251
|
-
actionId: LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID,
|
|
1252
|
-
} as LegendAIFallbackAction,
|
|
1253
|
-
}
|
|
1254
|
-
: {};
|
|
1255
|
-
updateLastAssistant(setMessages, () => ({
|
|
1256
|
-
textAnswer: buildZeroRowMessage(services),
|
|
1257
|
-
...fallback,
|
|
1258
|
-
isProcessing: false,
|
|
1259
|
-
isExecuting: false,
|
|
1260
|
-
thinkingDuration: elapsedSeconds(startTime),
|
|
1261
|
-
}));
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
async function selectBestServices(
|
|
1266
|
-
question: string,
|
|
1267
|
-
services: TDSServiceSchema[],
|
|
1268
|
-
context: LegendAIOperationContext,
|
|
1269
|
-
): Promise<TDSServiceSchema[]> {
|
|
1270
|
-
const { plugin, config, setMessages } = context;
|
|
1271
|
-
if (services.length <= 1) {
|
|
1272
|
-
return services;
|
|
1273
|
-
}
|
|
1274
|
-
try {
|
|
1275
|
-
addThinkingStep(setMessages, 'Selecting best service for your query...');
|
|
1276
|
-
return await plugin.selectRelevantServices(question, services, config);
|
|
1277
|
-
} catch {
|
|
1278
|
-
return services;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
async function tryRecoverZeroRows(
|
|
1283
|
-
currentSql: string,
|
|
1284
|
-
sqlResult: LegendAISqlExecutionResultData,
|
|
1285
|
-
question: string,
|
|
1286
|
-
selectedServices: TDSServiceSchema[],
|
|
1287
|
-
coordinates: string,
|
|
1288
|
-
dataProductCoordinates:
|
|
1289
|
-
| LegendAIOrchestratorDataProductCoordinates
|
|
1290
|
-
| undefined,
|
|
1291
|
-
context: LegendAIOperationContext,
|
|
1292
|
-
): Promise<{ sql: string; result: LegendAISqlExecutionResultData }> {
|
|
1293
|
-
const { plugin, config, setMessages } = context;
|
|
1294
|
-
let recoveredSql = currentSql;
|
|
1295
|
-
let recoveredResult = sqlResult;
|
|
1296
|
-
|
|
1297
|
-
const strippedSql = stripNonDateServiceParams(recoveredSql);
|
|
1298
|
-
if (strippedSql !== recoveredSql) {
|
|
1299
|
-
addThinkingStep(
|
|
1300
|
-
setMessages,
|
|
1301
|
-
'Trying query without guessed parameter values...',
|
|
1302
|
-
);
|
|
1303
|
-
try {
|
|
1304
|
-
const strippedResult = await executeSqlForServices(
|
|
1305
|
-
strippedSql,
|
|
1306
|
-
selectedServices,
|
|
1307
|
-
dataProductCoordinates,
|
|
1308
|
-
plugin,
|
|
1309
|
-
config,
|
|
617
|
+
pureExecutionContext,
|
|
1310
618
|
);
|
|
1311
|
-
|
|
1312
|
-
recoveredSql = strippedSql;
|
|
1313
|
-
recoveredResult = strippedResult;
|
|
1314
|
-
updateLastAssistant(setMessages, () => ({ sql: strippedSql }));
|
|
1315
|
-
}
|
|
1316
|
-
} catch {
|
|
1317
|
-
/* empty */
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
if (recoveredResult.rows.length === 0) {
|
|
1322
|
-
const correction = await attemptZeroRowCorrection(
|
|
1323
|
-
recoveredSql,
|
|
1324
|
-
question,
|
|
1325
|
-
selectedServices,
|
|
1326
|
-
coordinates,
|
|
1327
|
-
dataProductCoordinates,
|
|
1328
|
-
context,
|
|
1329
|
-
);
|
|
1330
|
-
if (correction) {
|
|
1331
|
-
recoveredSql = correction.sql;
|
|
1332
|
-
recoveredResult = correction.result;
|
|
619
|
+
return;
|
|
1333
620
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
return { sql: recoveredSql, result: recoveredResult };
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
async function processDataQuery(
|
|
1340
|
-
question: string,
|
|
1341
|
-
services: TDSServiceSchema[],
|
|
1342
|
-
coordinates: string,
|
|
1343
|
-
metadata: LegendAIProductMetadata,
|
|
1344
|
-
context: LegendAIOperationContext,
|
|
1345
|
-
startTime: number,
|
|
1346
|
-
orchestratorOptions?: LegendAIOrchestratorOptionsParam,
|
|
1347
|
-
): Promise<void> {
|
|
1348
|
-
const { config, setMessages } = context;
|
|
1349
|
-
const dataProductCoordinates = orchestratorOptions?.dataProductCoordinates;
|
|
1350
|
-
const hasOrchestratorFallback = Boolean(
|
|
1351
|
-
config.orchestratorUrl && dataProductCoordinates,
|
|
1352
|
-
);
|
|
1353
|
-
|
|
1354
|
-
if (services.length === 0) {
|
|
1355
|
-
handleSqlGenerationFailure(
|
|
621
|
+
finishWithThinkingError(
|
|
1356
622
|
setMessages,
|
|
1357
|
-
startTime,
|
|
1358
|
-
hasOrchestratorFallback,
|
|
1359
|
-
'No TDS services available for SQL querying. You can try the Legend AI Orchestrator to generate a Pure query instead.',
|
|
1360
623
|
'No TDS services available for querying',
|
|
1361
|
-
|
|
624
|
+
startTime,
|
|
1362
625
|
);
|
|
1363
626
|
return;
|
|
1364
627
|
}
|
|
1365
628
|
|
|
1366
629
|
addThinkingStep(setMessages, 'Found relevant services to query');
|
|
1367
630
|
|
|
1368
|
-
const selectedServices = await selectBestServices(
|
|
1369
|
-
question,
|
|
1370
|
-
services,
|
|
1371
|
-
context,
|
|
1372
|
-
);
|
|
1373
|
-
|
|
1374
631
|
const judgedSql = await generateAndJudgeSql(
|
|
1375
632
|
question,
|
|
1376
|
-
|
|
633
|
+
services,
|
|
1377
634
|
coordinates,
|
|
1378
635
|
context,
|
|
1379
636
|
startTime,
|
|
1380
637
|
);
|
|
1381
638
|
|
|
1382
639
|
if (!judgedSql) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
setMessages,
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
640
|
+
if (config.orchestratorUrl && dataProductCoordinates) {
|
|
641
|
+
addThinkingStep(
|
|
642
|
+
setMessages,
|
|
643
|
+
'SQL generation could not handle this query, trying Legend AI orchestrator...',
|
|
644
|
+
);
|
|
645
|
+
updateLastAssistant(setMessages, () => ({
|
|
646
|
+
error: null,
|
|
647
|
+
isProcessing: true,
|
|
648
|
+
}));
|
|
649
|
+
await processQuestionViaOrchestrator(
|
|
650
|
+
question,
|
|
651
|
+
dataProductCoordinates,
|
|
652
|
+
metadata,
|
|
653
|
+
context,
|
|
654
|
+
pureExecutionContext,
|
|
655
|
+
);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
1395
658
|
return;
|
|
1396
659
|
}
|
|
1397
660
|
|
|
1398
|
-
const sqlGenTimeValue =
|
|
661
|
+
const sqlGenTimeValue = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
1399
662
|
completeThinkingSteps(setMessages);
|
|
1400
663
|
updateLastAssistant(setMessages, () => ({
|
|
1401
664
|
sql: judgedSql,
|
|
@@ -1403,76 +666,39 @@ async function processDataQuery(
|
|
|
1403
666
|
isExecuting: true,
|
|
1404
667
|
}));
|
|
1405
668
|
|
|
1406
|
-
const
|
|
669
|
+
const sqlResult = await executeSqlAndReport(
|
|
1407
670
|
judgedSql,
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
671
|
+
services,
|
|
672
|
+
config,
|
|
673
|
+
plugin,
|
|
674
|
+
setMessages,
|
|
675
|
+
startTime,
|
|
1411
676
|
dataProductCoordinates,
|
|
1412
|
-
context,
|
|
1413
677
|
);
|
|
1414
678
|
|
|
1415
|
-
if (
|
|
1416
|
-
|
|
679
|
+
if (
|
|
680
|
+
sqlResult?.rows.length === 0 &&
|
|
681
|
+
config.orchestratorUrl &&
|
|
682
|
+
dataProductCoordinates
|
|
683
|
+
) {
|
|
1417
684
|
addThinkingStep(
|
|
1418
685
|
setMessages,
|
|
1419
|
-
|
|
1420
|
-
);
|
|
1421
|
-
finishWithThinkingError(
|
|
1422
|
-
setMessages,
|
|
1423
|
-
buildExecutionErrorMessage(execOutcome.error, selectedServices),
|
|
1424
|
-
startTime,
|
|
1425
|
-
execErrorType === LegendAIErrorType.GENERAL
|
|
1426
|
-
? LegendAIErrorType.EXECUTION
|
|
1427
|
-
: execErrorType,
|
|
686
|
+
'SQL query returned no results, trying Legend AI orchestrator...',
|
|
1428
687
|
);
|
|
1429
688
|
updateLastAssistant(setMessages, () => ({
|
|
689
|
+
gridData: null,
|
|
690
|
+
error: null,
|
|
691
|
+
isProcessing: true,
|
|
1430
692
|
isExecuting: false,
|
|
1431
|
-
...(hasOrchestratorFallback
|
|
1432
|
-
? {
|
|
1433
|
-
fallbackAction: {
|
|
1434
|
-
label: 'Try Legend AI Orchestrator',
|
|
1435
|
-
actionId: LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID,
|
|
1436
|
-
} as LegendAIFallbackAction,
|
|
1437
|
-
}
|
|
1438
|
-
: {}),
|
|
1439
693
|
}));
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
if (!execOutcome.result) {
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
let currentSql = execOutcome.sql;
|
|
1448
|
-
let sqlResult = execOutcome.result;
|
|
1449
|
-
|
|
1450
|
-
if (sqlResult.rows.length === 0) {
|
|
1451
|
-
const recovered = await tryRecoverZeroRows(
|
|
1452
|
-
currentSql,
|
|
1453
|
-
sqlResult,
|
|
694
|
+
await processQuestionViaOrchestrator(
|
|
1454
695
|
question,
|
|
1455
|
-
selectedServices,
|
|
1456
|
-
coordinates,
|
|
1457
696
|
dataProductCoordinates,
|
|
697
|
+
metadata,
|
|
1458
698
|
context,
|
|
699
|
+
pureExecutionContext,
|
|
1459
700
|
);
|
|
1460
|
-
currentSql = recovered.sql;
|
|
1461
|
-
sqlResult = recovered.result;
|
|
1462
701
|
}
|
|
1463
|
-
|
|
1464
|
-
await reportQueryResults(
|
|
1465
|
-
{
|
|
1466
|
-
currentSql,
|
|
1467
|
-
sqlResult,
|
|
1468
|
-
question,
|
|
1469
|
-
services: selectedServices,
|
|
1470
|
-
},
|
|
1471
|
-
metadata,
|
|
1472
|
-
context,
|
|
1473
|
-
startTime,
|
|
1474
|
-
hasOrchestratorFallback,
|
|
1475
|
-
);
|
|
1476
702
|
}
|
|
1477
703
|
|
|
1478
704
|
export async function processQuestion(
|
|
@@ -1490,96 +716,66 @@ export async function processQuestion(
|
|
|
1490
716
|
try {
|
|
1491
717
|
addThinkingStep(setMessages, 'Analyzing your question...');
|
|
1492
718
|
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const serviceNames = services.map((s) => s.title);
|
|
1504
|
-
const intent = await plugin.classifyQuestionIntent(
|
|
719
|
+
const serviceNames = services.map((s) => s.title);
|
|
720
|
+
const intent = await plugin.classifyQuestionIntent(
|
|
721
|
+
question,
|
|
722
|
+
services.length > 0,
|
|
723
|
+
config,
|
|
724
|
+
serviceNames,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
if (intent === LegendAIQuestionIntent.METADATA) {
|
|
728
|
+
await handleMetadataQuestion(
|
|
1505
729
|
question,
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
730
|
+
metadata,
|
|
731
|
+
context,
|
|
732
|
+
startTime,
|
|
733
|
+
services.length > 0,
|
|
1509
734
|
);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
1510
737
|
|
|
1511
|
-
|
|
1512
|
-
|
|
738
|
+
if (intent === LegendAIQuestionIntent.ORCHESTRATOR) {
|
|
739
|
+
if (config.orchestratorUrl && dataProductCoordinates) {
|
|
740
|
+
completeThinkingSteps(setMessages);
|
|
741
|
+
await processQuestionViaOrchestrator(
|
|
1513
742
|
question,
|
|
743
|
+
dataProductCoordinates,
|
|
1514
744
|
metadata,
|
|
1515
745
|
context,
|
|
1516
|
-
|
|
1517
|
-
true,
|
|
746
|
+
pureExecutionContext,
|
|
1518
747
|
);
|
|
1519
748
|
return;
|
|
1520
749
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
// (e.g. misclassified metadata question).
|
|
1525
|
-
try {
|
|
1526
|
-
await processDataQuery(
|
|
1527
|
-
question,
|
|
1528
|
-
services,
|
|
1529
|
-
coordinates,
|
|
1530
|
-
metadata,
|
|
1531
|
-
context,
|
|
1532
|
-
startTime,
|
|
1533
|
-
orchestratorOpts,
|
|
1534
|
-
);
|
|
1535
|
-
} catch (sqlError) {
|
|
1536
|
-
assertErrorThrown(sqlError);
|
|
1537
|
-
addThinkingStep(
|
|
1538
|
-
setMessages,
|
|
1539
|
-
'SQL generation failed, answering from product metadata...',
|
|
1540
|
-
);
|
|
1541
|
-
await handleMetadataQuestion(
|
|
1542
|
-
question,
|
|
1543
|
-
metadata,
|
|
1544
|
-
context,
|
|
1545
|
-
startTime,
|
|
1546
|
-
true,
|
|
1547
|
-
);
|
|
1548
|
-
}
|
|
1549
|
-
return;
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
// No services available — use orchestrator if configured, else metadata only.
|
|
1553
|
-
if (config.orchestratorUrl && dataProductCoordinates) {
|
|
1554
|
-
completeThinkingSteps(setMessages);
|
|
1555
|
-
await processQuestionViaOrchestrator(
|
|
1556
|
-
question,
|
|
1557
|
-
dataProductCoordinates,
|
|
1558
|
-
metadata,
|
|
1559
|
-
context,
|
|
1560
|
-
pureExecutionContext,
|
|
1561
|
-
);
|
|
1562
|
-
} else {
|
|
1563
|
-
await handleMetadataQuestion(
|
|
1564
|
-
question,
|
|
1565
|
-
metadata,
|
|
1566
|
-
context,
|
|
1567
|
-
startTime,
|
|
1568
|
-
false,
|
|
750
|
+
addThinkingStep(
|
|
751
|
+
setMessages,
|
|
752
|
+
'Orchestrator not available, trying SQL generation...',
|
|
1569
753
|
);
|
|
1570
754
|
}
|
|
755
|
+
|
|
756
|
+
await processDataQuery(
|
|
757
|
+
question,
|
|
758
|
+
services,
|
|
759
|
+
coordinates,
|
|
760
|
+
metadata,
|
|
761
|
+
context,
|
|
762
|
+
startTime,
|
|
763
|
+
dataProductCoordinates
|
|
764
|
+
? {
|
|
765
|
+
dataProductCoordinates,
|
|
766
|
+
...(pureExecutionContext === undefined
|
|
767
|
+
? {}
|
|
768
|
+
: { pureExecutionContext }),
|
|
769
|
+
}
|
|
770
|
+
: undefined,
|
|
771
|
+
);
|
|
1571
772
|
} catch (error) {
|
|
1572
773
|
assertErrorThrown(error);
|
|
1573
774
|
addThinkingStep(
|
|
1574
775
|
setMessages,
|
|
1575
776
|
`Error: ${error.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
1576
777
|
);
|
|
1577
|
-
finishWithThinkingError(
|
|
1578
|
-
setMessages,
|
|
1579
|
-
error.message,
|
|
1580
|
-
startTime,
|
|
1581
|
-
classifyError(error),
|
|
1582
|
-
);
|
|
778
|
+
finishWithThinkingError(setMessages, error.message, startTime);
|
|
1583
779
|
}
|
|
1584
780
|
}
|
|
1585
781
|
|
|
@@ -1598,61 +794,18 @@ export async function processQuestionWithIntent(
|
|
|
1598
794
|
|
|
1599
795
|
if (intent === LegendAIQuestionIntent.METADATA) {
|
|
1600
796
|
const startTime = Date.now();
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
);
|
|
1609
|
-
} catch (error) {
|
|
1610
|
-
assertErrorThrown(error);
|
|
1611
|
-
addThinkingStep(
|
|
1612
|
-
setMessages,
|
|
1613
|
-
`Error: ${error.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
1614
|
-
);
|
|
1615
|
-
finishWithThinkingError(
|
|
1616
|
-
setMessages,
|
|
1617
|
-
error.message,
|
|
1618
|
-
startTime,
|
|
1619
|
-
classifyError(error),
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
797
|
+
await handleMetadataQuestion(
|
|
798
|
+
question,
|
|
799
|
+
metadata,
|
|
800
|
+
context,
|
|
801
|
+
startTime,
|
|
802
|
+
services.length > 0,
|
|
803
|
+
);
|
|
1622
804
|
return;
|
|
1623
805
|
}
|
|
1624
806
|
|
|
1625
807
|
if (intent === LegendAIQuestionIntent.ORCHESTRATOR) {
|
|
1626
808
|
if (config.orchestratorUrl && dataProductCoordinates) {
|
|
1627
|
-
// When services are available, try SQL first even for ORCHESTRATOR intent
|
|
1628
|
-
if (services.length > 0) {
|
|
1629
|
-
const startTime = Date.now();
|
|
1630
|
-
try {
|
|
1631
|
-
addThinkingStep(setMessages, 'Preparing data query...');
|
|
1632
|
-
await processDataQuery(
|
|
1633
|
-
question,
|
|
1634
|
-
services,
|
|
1635
|
-
coordinates,
|
|
1636
|
-
metadata,
|
|
1637
|
-
context,
|
|
1638
|
-
startTime,
|
|
1639
|
-
orchestratorOptions,
|
|
1640
|
-
);
|
|
1641
|
-
} catch (error) {
|
|
1642
|
-
assertErrorThrown(error);
|
|
1643
|
-
addThinkingStep(
|
|
1644
|
-
setMessages,
|
|
1645
|
-
`Error: ${error.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
1646
|
-
);
|
|
1647
|
-
finishWithThinkingError(
|
|
1648
|
-
setMessages,
|
|
1649
|
-
error.message,
|
|
1650
|
-
startTime,
|
|
1651
|
-
classifyError(error),
|
|
1652
|
-
);
|
|
1653
|
-
}
|
|
1654
|
-
return;
|
|
1655
|
-
}
|
|
1656
809
|
await processQuestionViaOrchestrator(
|
|
1657
810
|
question,
|
|
1658
811
|
dataProductCoordinates,
|
|
@@ -1683,12 +836,7 @@ export async function processQuestionWithIntent(
|
|
|
1683
836
|
setMessages,
|
|
1684
837
|
`Error: ${error.message.slice(0, MAX_THINKING_ERROR_PREVIEW_LENGTH)}`,
|
|
1685
838
|
);
|
|
1686
|
-
finishWithThinkingError(
|
|
1687
|
-
setMessages,
|
|
1688
|
-
error.message,
|
|
1689
|
-
startTime,
|
|
1690
|
-
classifyError(error),
|
|
1691
|
-
);
|
|
839
|
+
finishWithThinkingError(setMessages, error.message, startTime);
|
|
1692
840
|
}
|
|
1693
841
|
}
|
|
1694
842
|
|
|
@@ -1841,64 +989,6 @@ export const useLegendAIChatState = (
|
|
|
1841
989
|
],
|
|
1842
990
|
);
|
|
1843
991
|
|
|
1844
|
-
const runFallbackAction = useCallback(
|
|
1845
|
-
(messageId: string): void => {
|
|
1846
|
-
if (isSending || !config.orchestratorUrl || !dataProductCoordinates) {
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
// Find the user question associated with this assistant message
|
|
1850
|
-
let question: string | undefined;
|
|
1851
|
-
for (let i = 0; i < messages.length; i++) {
|
|
1852
|
-
const msg = messages[i];
|
|
1853
|
-
if (
|
|
1854
|
-
msg?.role === LegendAIMessageRole.ASSISTANT &&
|
|
1855
|
-
msg.id === messageId &&
|
|
1856
|
-
i > 0
|
|
1857
|
-
) {
|
|
1858
|
-
const userMsg = messages[i - 1];
|
|
1859
|
-
if (userMsg?.role === LegendAIMessageRole.USER) {
|
|
1860
|
-
question = userMsg.text;
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
if (!question) {
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
setIsSending(true);
|
|
1869
|
-
setMessages((prev) =>
|
|
1870
|
-
prev.map((m) =>
|
|
1871
|
-
m.id === messageId && m.role === LegendAIMessageRole.ASSISTANT
|
|
1872
|
-
? { ...m, fallbackAction: null, error: null, isProcessing: true }
|
|
1873
|
-
: m,
|
|
1874
|
-
),
|
|
1875
|
-
);
|
|
1876
|
-
|
|
1877
|
-
const history = buildConversationHistory(messages);
|
|
1878
|
-
const q = question;
|
|
1879
|
-
processQuestionViaOrchestrator(
|
|
1880
|
-
q,
|
|
1881
|
-
dataProductCoordinates,
|
|
1882
|
-
metadata,
|
|
1883
|
-
{ config, plugin, history, setMessages },
|
|
1884
|
-
pureExecutionContext,
|
|
1885
|
-
)
|
|
1886
|
-
.catch(noop())
|
|
1887
|
-
.finally(() => {
|
|
1888
|
-
setIsSending(false);
|
|
1889
|
-
});
|
|
1890
|
-
},
|
|
1891
|
-
[
|
|
1892
|
-
isSending,
|
|
1893
|
-
messages,
|
|
1894
|
-
config,
|
|
1895
|
-
metadata,
|
|
1896
|
-
plugin,
|
|
1897
|
-
dataProductCoordinates,
|
|
1898
|
-
pureExecutionContext,
|
|
1899
|
-
],
|
|
1900
|
-
);
|
|
1901
|
-
|
|
1902
992
|
return {
|
|
1903
993
|
questionText,
|
|
1904
994
|
setQuestionText,
|
|
@@ -1906,7 +996,6 @@ export const useLegendAIChatState = (
|
|
|
1906
996
|
messages,
|
|
1907
997
|
askQuestion,
|
|
1908
998
|
askQuestionWithIntent,
|
|
1909
|
-
runFallbackAction,
|
|
1910
999
|
clearChat,
|
|
1911
1000
|
expandedThinking,
|
|
1912
1001
|
toggleThinking,
|