@finos/legend-lego 2.0.217 → 2.0.218
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/LegendAIDocEnrichment.d.ts +14 -0
- package/lib/legend-ai/LegendAIDocEnrichment.d.ts.map +1 -1
- package/lib/legend-ai/LegendAIDocEnrichment.js +140 -17
- package/lib/legend-ai/LegendAIDocEnrichment.js.map +1 -1
- package/lib/legend-ai/LegendAITypes.d.ts +30 -1
- package/lib/legend-ai/LegendAITypes.d.ts.map +1 -1
- package/lib/legend-ai/LegendAITypes.js +31 -0
- package/lib/legend-ai/LegendAITypes.js.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts +2 -2
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.js +62 -38
- package/lib/legend-ai/components/LegendAIChat.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChatHelpers.d.ts +5 -0
- package/lib/legend-ai/components/LegendAIChatHelpers.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChatHelpers.js +8 -1
- package/lib/legend-ai/components/LegendAIChatHelpers.js.map +1 -1
- package/lib/legend-ai/index.d.ts +3 -2
- package/lib/legend-ai/index.d.ts.map +1 -1
- package/lib/legend-ai/index.js +3 -2
- package/lib/legend-ai/index.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatProcessors.d.ts +27 -2
- package/lib/legend-ai/stores/LegendAIChatProcessors.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatProcessors.js +329 -86
- package/lib/legend-ai/stores/LegendAIChatProcessors.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.d.ts +11 -0
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.js +53 -19
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.js.map +1 -1
- package/lib/legend-ai/stores/LegendAISqlHelpers.d.ts +4 -1
- package/lib/legend-ai/stores/LegendAISqlHelpers.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAISqlHelpers.js +25 -2
- package/lib/legend-ai/stores/LegendAISqlHelpers.js.map +1 -1
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.js +4 -4
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.js.map +1 -1
- package/package.json +3 -3
- package/src/legend-ai/LegendAIDocEnrichment.ts +189 -23
- package/src/legend-ai/LegendAITypes.ts +54 -1
- package/src/legend-ai/LegendAI_LegendApplicationPlugin_Extension.ts +2 -0
- package/src/legend-ai/components/LegendAIChat.tsx +114 -78
- package/src/legend-ai/components/LegendAIChatHelpers.ts +9 -3
- package/src/legend-ai/index.ts +7 -0
- package/src/legend-ai/stores/LegendAIChatProcessors.ts +476 -105
- package/src/legend-ai/stores/LegendAIJoinAnalysis.ts +82 -19
- package/src/legend-ai/stores/LegendAISqlHelpers.ts +32 -2
- package/src/legend-ai/stores/LegendAISqlJoinSanitizers.ts +4 -4
|
@@ -15,11 +15,17 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
+
type AbstractPureGraphManager,
|
|
18
19
|
type Multiplicity,
|
|
19
20
|
extractElementNameFromPath,
|
|
20
21
|
PRIMITIVE_TYPE,
|
|
22
|
+
GRAPH_MANAGER_EVENT,
|
|
21
23
|
} from '@finos/legend-graph';
|
|
22
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
assertErrorThrown,
|
|
26
|
+
isNonNullable,
|
|
27
|
+
LogEvent,
|
|
28
|
+
} from '@finos/legend-shared';
|
|
23
29
|
import {
|
|
24
30
|
ClassDocumentationEntry,
|
|
25
31
|
AssociationDocumentationEntry,
|
|
@@ -36,6 +42,7 @@ import type {
|
|
|
36
42
|
LegendAIModelEntity,
|
|
37
43
|
LegendAIModelAssociation,
|
|
38
44
|
LegendAIModelProperty,
|
|
45
|
+
LegendAIFunctionInfo,
|
|
39
46
|
} from './LegendAITypes.js';
|
|
40
47
|
import type {
|
|
41
48
|
LegendAIEnrichedBusinessContext,
|
|
@@ -247,6 +254,8 @@ function findDirectRelationship(
|
|
|
247
254
|
return undefined;
|
|
248
255
|
}
|
|
249
256
|
|
|
257
|
+
const MAX_SERVICE_RELATIONSHIPS = 25;
|
|
258
|
+
|
|
250
259
|
/**
|
|
251
260
|
* Infers cross-service relationships from association documentation entries.
|
|
252
261
|
*
|
|
@@ -258,6 +267,9 @@ function findDirectRelationship(
|
|
|
258
267
|
* - Which columns to JOIN on (shared column names)
|
|
259
268
|
*
|
|
260
269
|
* Used by both Data Space and Data Product AI integrations.
|
|
270
|
+
*
|
|
271
|
+
* Capped at {@link MAX_SERVICE_RELATIONSHIPS}: the pair space is quadratic in
|
|
272
|
+
* the service count, and every entry is rendered into the metadata prompt.
|
|
261
273
|
*/
|
|
262
274
|
export function inferServiceRelationshipsFromAssociations(
|
|
263
275
|
services: TDSServiceSchema[],
|
|
@@ -268,12 +280,10 @@ export function inferServiceRelationshipsFromAssociations(
|
|
|
268
280
|
const relationships: LegendAIServiceRelationship[] = [];
|
|
269
281
|
const seen = new Set<string>();
|
|
270
282
|
|
|
271
|
-
for (
|
|
272
|
-
for (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (!svcA || !svcB) {
|
|
276
|
-
continue;
|
|
283
|
+
for (const [index, svcA] of services.entries()) {
|
|
284
|
+
for (const svcB of services.slice(index + 1)) {
|
|
285
|
+
if (relationships.length >= MAX_SERVICE_RELATIONSHIPS) {
|
|
286
|
+
return relationships;
|
|
277
287
|
}
|
|
278
288
|
const key = `${svcA.title}|${svcB.title}`;
|
|
279
289
|
if (seen.has(key)) {
|
|
@@ -542,6 +552,28 @@ export function extractLambdaPreFilters(
|
|
|
542
552
|
return results;
|
|
543
553
|
}
|
|
544
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Reads the filters already baked into a service query. Best effort: without
|
|
557
|
+
* them the service still works, the AI just misses its hardcoded constraints.
|
|
558
|
+
*/
|
|
559
|
+
export async function extractServicePreFilters(
|
|
560
|
+
query: string,
|
|
561
|
+
graphManager: AbstractPureGraphManager,
|
|
562
|
+
): Promise<TDSServicePreFilter[] | undefined> {
|
|
563
|
+
try {
|
|
564
|
+
const rawLambda = await graphManager.pureCodeToLambda(query);
|
|
565
|
+
const preFilters = extractLambdaPreFilters(rawLambda.body);
|
|
566
|
+
return preFilters.length > 0 ? preFilters : undefined;
|
|
567
|
+
} catch (error) {
|
|
568
|
+
assertErrorThrown(error);
|
|
569
|
+
graphManager.logService.debug(
|
|
570
|
+
LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE),
|
|
571
|
+
error,
|
|
572
|
+
);
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
545
577
|
// ────────────────────────────────────────────────────────────────────────────
|
|
546
578
|
// Model context extraction from DataSpace elementDocs
|
|
547
579
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -577,6 +609,9 @@ function collectClassEntities(
|
|
|
577
609
|
if (cls.docs.length > 0) {
|
|
578
610
|
entity.description = cls.docs.join('; ');
|
|
579
611
|
}
|
|
612
|
+
if (cls.milestoning !== undefined) {
|
|
613
|
+
entity.milestoning = cls.milestoning;
|
|
614
|
+
}
|
|
580
615
|
return entity;
|
|
581
616
|
});
|
|
582
617
|
}
|
|
@@ -742,6 +777,13 @@ function buildRootEntityNlHints(
|
|
|
742
777
|
category: 'queryable_hint',
|
|
743
778
|
});
|
|
744
779
|
}
|
|
780
|
+
if (rootEntityObj.milestoning) {
|
|
781
|
+
entries.push({
|
|
782
|
+
id: rootEntity,
|
|
783
|
+
description: `${rootEntityObj.name} is temporally versioned (${rootEntityObj.milestoning}) — include an as-of/business-date filter to select the intended snapshot`,
|
|
784
|
+
category: 'milestoning_hint',
|
|
785
|
+
});
|
|
786
|
+
}
|
|
745
787
|
return entries;
|
|
746
788
|
}
|
|
747
789
|
|
|
@@ -982,6 +1024,7 @@ function buildNlContextEntries(
|
|
|
982
1024
|
entries.push(
|
|
983
1025
|
...buildAssociationNlHints(rootEntity, relatedEntities, modelContext),
|
|
984
1026
|
...buildExecutableNlHints(rootEntity, rootEntityObj, modelContext),
|
|
1027
|
+
...buildFunctionNlHints(modelContext),
|
|
985
1028
|
...buildValidValueNlHints(
|
|
986
1029
|
rootEntity,
|
|
987
1030
|
relatedEntities,
|
|
@@ -1185,6 +1228,18 @@ export function buildSemanticPropertyIndex(
|
|
|
1185
1228
|
// ────────────────────────────────────────────────────────────────────────────
|
|
1186
1229
|
|
|
1187
1230
|
const MAX_RESOLVED_RELATED_ENTITIES = 5;
|
|
1231
|
+
const EXECUTABLE_COUNT_SCORE_CAP = 3;
|
|
1232
|
+
|
|
1233
|
+
// Counts executables whose root is each entity — a signal of "real query target".
|
|
1234
|
+
function buildExecutableCountByRoot(
|
|
1235
|
+
modelContext: LegendAIModelContext,
|
|
1236
|
+
): Map<string, number> {
|
|
1237
|
+
const counts = new Map<string, number>();
|
|
1238
|
+
for (const exec of modelContext.executables ?? []) {
|
|
1239
|
+
counts.set(exec.rootEntityPath, (counts.get(exec.rootEntityPath) ?? 0) + 1);
|
|
1240
|
+
}
|
|
1241
|
+
return counts;
|
|
1242
|
+
}
|
|
1188
1243
|
|
|
1189
1244
|
/**
|
|
1190
1245
|
* Resolves root and related entities using keyword matching against the model
|
|
@@ -1199,6 +1254,7 @@ const MAX_RESOLVED_RELATED_ENTITIES = 5;
|
|
|
1199
1254
|
* - description keyword match (+1)
|
|
1200
1255
|
* - executable title/description match (+4)
|
|
1201
1256
|
* - semantic property-doc match (+1 per token hit)
|
|
1257
|
+
* - executable count on the entity (+1 each, capped at 3)
|
|
1202
1258
|
*
|
|
1203
1259
|
* When queryable entities exist, only they are eligible as root.
|
|
1204
1260
|
*/
|
|
@@ -1274,12 +1330,17 @@ function scoreEntityByProperties(
|
|
|
1274
1330
|
return score;
|
|
1275
1331
|
}
|
|
1276
1332
|
|
|
1333
|
+
interface EntityScoreIndexes {
|
|
1334
|
+
semanticIndex: Map<string, Set<string>>;
|
|
1335
|
+
executableByRoot: Map<string, string[]>;
|
|
1336
|
+
executableCountByRoot: Map<string, number>;
|
|
1337
|
+
enumValueIndex: Map<string, Set<string>>;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1277
1340
|
function scoreEntity(
|
|
1278
1341
|
entity: LegendAIModelEntity,
|
|
1279
1342
|
tokens: string[],
|
|
1280
|
-
|
|
1281
|
-
executableByRoot: Map<string, string[]>,
|
|
1282
|
-
enumValueIndex: Map<string, Set<string>>,
|
|
1343
|
+
indexes: EntityScoreIndexes,
|
|
1283
1344
|
): number {
|
|
1284
1345
|
const nameLower = entity.name.toLowerCase();
|
|
1285
1346
|
let score =
|
|
@@ -1295,15 +1356,22 @@ function scoreEntity(
|
|
|
1295
1356
|
const descLower = entity.description.toLowerCase();
|
|
1296
1357
|
score += tokens.filter((t) => descLower.includes(t)).length;
|
|
1297
1358
|
}
|
|
1298
|
-
const execWords = executableByRoot.get(entity.path);
|
|
1359
|
+
const execWords = indexes.executableByRoot.get(entity.path);
|
|
1299
1360
|
if (execWords) {
|
|
1300
1361
|
score +=
|
|
1301
1362
|
tokens.filter((t) => execWords.some((w) => w === t || w.includes(t)))
|
|
1302
1363
|
.length * 4;
|
|
1303
1364
|
}
|
|
1304
|
-
score +=
|
|
1365
|
+
score += Math.min(
|
|
1366
|
+
indexes.executableCountByRoot.get(entity.path) ?? 0,
|
|
1367
|
+
EXECUTABLE_COUNT_SCORE_CAP,
|
|
1368
|
+
);
|
|
1369
|
+
score += tokens.filter((t) =>
|
|
1370
|
+
indexes.semanticIndex.get(t)?.has(entity.path),
|
|
1371
|
+
).length;
|
|
1305
1372
|
score +=
|
|
1306
|
-
tokens.filter((t) => enumValueIndex.get(t)?.has(entity.path))
|
|
1373
|
+
tokens.filter((t) => indexes.enumValueIndex.get(t)?.has(entity.path))
|
|
1374
|
+
.length * 2;
|
|
1307
1375
|
return score;
|
|
1308
1376
|
}
|
|
1309
1377
|
|
|
@@ -1311,20 +1379,17 @@ export function rankEntities(
|
|
|
1311
1379
|
question: string,
|
|
1312
1380
|
modelContext: LegendAIModelContext,
|
|
1313
1381
|
): { entity: LegendAIModelEntity; score: number }[] {
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
|
|
1382
|
+
const indexes: EntityScoreIndexes = {
|
|
1383
|
+
semanticIndex: buildSemanticPropertyIndex(modelContext),
|
|
1384
|
+
executableByRoot: buildExecutableByRootIndex(modelContext),
|
|
1385
|
+
executableCountByRoot: buildExecutableCountByRoot(modelContext),
|
|
1386
|
+
enumValueIndex: buildEnumValueIndex(modelContext),
|
|
1387
|
+
};
|
|
1317
1388
|
const tokens = tokenizeText(question);
|
|
1318
1389
|
return modelContext.entities
|
|
1319
1390
|
.map((entity) => ({
|
|
1320
1391
|
entity,
|
|
1321
|
-
score: scoreEntity(
|
|
1322
|
-
entity,
|
|
1323
|
-
tokens,
|
|
1324
|
-
semanticIndex,
|
|
1325
|
-
executableByRoot,
|
|
1326
|
-
enumValueIndex,
|
|
1327
|
-
),
|
|
1392
|
+
score: scoreEntity(entity, tokens, indexes),
|
|
1328
1393
|
}))
|
|
1329
1394
|
.sort((a, b) => b.score - a.score);
|
|
1330
1395
|
}
|
|
@@ -1490,6 +1555,34 @@ export function extractFilteredColumns(pureQuery: string): string[] {
|
|
|
1490
1555
|
|
|
1491
1556
|
const MAX_PROBED_VALUES_PER_COLUMN = 30;
|
|
1492
1557
|
|
|
1558
|
+
const MAX_PRIOR_SQL_HINT_CHARS = 2000;
|
|
1559
|
+
|
|
1560
|
+
/**
|
|
1561
|
+
* Builds orchestrator hints describing a prior SQL attempt that failed or
|
|
1562
|
+
* returned no rows, so the Pure query is generated with that context in mind.
|
|
1563
|
+
*/
|
|
1564
|
+
export function buildPriorSqlFailureHints(
|
|
1565
|
+
failedSql: string | undefined,
|
|
1566
|
+
failedReason: string | undefined,
|
|
1567
|
+
): LegendAIAdditionalNlModelContext[] {
|
|
1568
|
+
const entries: LegendAIAdditionalNlModelContext[] = [];
|
|
1569
|
+
if (failedReason !== undefined && failedReason.length > 0) {
|
|
1570
|
+
entries.push({
|
|
1571
|
+
id: 'prior_sql_failure_reason',
|
|
1572
|
+
description: `A prior SQL attempt for this question did not yield a usable answer: ${failedReason.slice(0, MAX_PRIOR_SQL_HINT_CHARS)}. Generate a query using the model's entities, relationships, and functions rather than repeating that approach.`,
|
|
1573
|
+
category: 'prior_sql_failure',
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
if (failedSql !== undefined && failedSql.length > 0) {
|
|
1577
|
+
entries.push({
|
|
1578
|
+
id: 'prior_sql_failure_query',
|
|
1579
|
+
description: `The prior SQL that failed or returned 0 rows was: ${failedSql.slice(0, MAX_PRIOR_SQL_HINT_CHARS)}. Avoid the filters or joins that produced no rows.`,
|
|
1580
|
+
category: 'prior_sql_failure',
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
return entries;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1493
1586
|
export function buildProbedValueHints(
|
|
1494
1587
|
probed: Map<string, string[]>,
|
|
1495
1588
|
): LegendAIAdditionalNlModelContext[] {
|
|
@@ -1689,6 +1782,75 @@ function buildExecutableSection(
|
|
|
1689
1782
|
return lines.join('\n');
|
|
1690
1783
|
}
|
|
1691
1784
|
|
|
1785
|
+
const MAX_MILESTONING_ENTITIES = 15;
|
|
1786
|
+
|
|
1787
|
+
// Lists temporally-versioned entities so the LLM adds as-of/business-date filters.
|
|
1788
|
+
function buildMilestoningSection(
|
|
1789
|
+
modelContext: LegendAIModelContext,
|
|
1790
|
+
): string | undefined {
|
|
1791
|
+
const temporal = modelContext.entities.filter((e) => e.milestoning);
|
|
1792
|
+
if (temporal.length === 0) {
|
|
1793
|
+
return undefined;
|
|
1794
|
+
}
|
|
1795
|
+
const lines = [
|
|
1796
|
+
'## Temporal Entities (time-versioned)',
|
|
1797
|
+
'These entities track changes over time — include an as-of/business-date filter to select the intended snapshot:',
|
|
1798
|
+
];
|
|
1799
|
+
for (const entity of temporal.slice(0, MAX_MILESTONING_ENTITIES)) {
|
|
1800
|
+
lines.push(`- ${entity.name} [${entity.milestoning}]`);
|
|
1801
|
+
}
|
|
1802
|
+
if (temporal.length > MAX_MILESTONING_ENTITIES) {
|
|
1803
|
+
lines.push(
|
|
1804
|
+
`\n(${temporal.length - MAX_MILESTONING_ENTITIES} additional temporal entities omitted)`,
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
return lines.join('\n');
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
const MAX_ENRICHMENT_FUNCTIONS = 20;
|
|
1811
|
+
|
|
1812
|
+
function formatFunctionSignature(fn: LegendAIFunctionInfo): string {
|
|
1813
|
+
const params = fn.parameters.map((p) => `${p.name}: ${p.type}`).join(', ');
|
|
1814
|
+
const returns = fn.returnType ? `: ${fn.returnType}` : '';
|
|
1815
|
+
return `${fn.name}(${params})${returns}`;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// Lists the dataspace's callable model functions so the orchestrator can use them.
|
|
1819
|
+
function buildFunctionSection(
|
|
1820
|
+
modelContext: LegendAIModelContext,
|
|
1821
|
+
): string | undefined {
|
|
1822
|
+
if (!modelContext.functions?.length) {
|
|
1823
|
+
return undefined;
|
|
1824
|
+
}
|
|
1825
|
+
const lines = [
|
|
1826
|
+
'## Available Functions',
|
|
1827
|
+
'These model functions can be called in queries:',
|
|
1828
|
+
];
|
|
1829
|
+
for (const fn of modelContext.functions.slice(0, MAX_ENRICHMENT_FUNCTIONS)) {
|
|
1830
|
+
lines.push(`- ${formatFunctionSignature(fn)}`);
|
|
1831
|
+
}
|
|
1832
|
+
if (modelContext.functions.length > MAX_ENRICHMENT_FUNCTIONS) {
|
|
1833
|
+
lines.push(
|
|
1834
|
+
`\n(${modelContext.functions.length - MAX_ENRICHMENT_FUNCTIONS} additional functions omitted)`,
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
return lines.join('\n');
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function buildFunctionNlHints(
|
|
1841
|
+
modelContext: LegendAIModelContext,
|
|
1842
|
+
): LegendAIAdditionalNlModelContext[] {
|
|
1843
|
+
return (modelContext.functions ?? [])
|
|
1844
|
+
.slice(0, MAX_ENRICHMENT_FUNCTIONS)
|
|
1845
|
+
.map((fn) => {
|
|
1846
|
+
return {
|
|
1847
|
+
id: fn.functionPath,
|
|
1848
|
+
description: `Callable function: ${formatFunctionSignature(fn)}`,
|
|
1849
|
+
category: 'function',
|
|
1850
|
+
};
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1692
1854
|
function buildPropToEnumPathMap(
|
|
1693
1855
|
modelContext: LegendAIModelContext,
|
|
1694
1856
|
enumPathMap: Map<string, string[]>,
|
|
@@ -1934,6 +2096,8 @@ export function buildModelContextEnrichmentText(
|
|
|
1934
2096
|
buildEnumerationSection(modelContext),
|
|
1935
2097
|
buildAssociationSection(modelContext),
|
|
1936
2098
|
buildExecutableSection(modelContext),
|
|
2099
|
+
buildFunctionSection(modelContext),
|
|
2100
|
+
buildMilestoningSection(modelContext),
|
|
1937
2101
|
buildColumnEnumMappingSection(modelContext, services),
|
|
1938
2102
|
buildColumnDescriptionSection(modelContext, services),
|
|
1939
2103
|
buildServiceJoinSection(modelContext, services),
|
|
@@ -2094,6 +2258,8 @@ export function buildModelCatalogText(
|
|
|
2094
2258
|
buildCatalogRelationshipsSection(modelContext),
|
|
2095
2259
|
buildCatalogEnumerationsSection(modelContext),
|
|
2096
2260
|
buildCatalogExecutablesSection(modelContext),
|
|
2261
|
+
buildFunctionSection(modelContext),
|
|
2262
|
+
buildMilestoningSection(modelContext),
|
|
2097
2263
|
].filter(isNonNullable);
|
|
2098
2264
|
|
|
2099
2265
|
if (sections.length === 0) {
|
|
@@ -37,6 +37,7 @@ export class TDSColumnSchema {
|
|
|
37
37
|
type?: string;
|
|
38
38
|
documentation?: string;
|
|
39
39
|
sampleValues?: string;
|
|
40
|
+
sampleValuesComplete?: boolean;
|
|
40
41
|
/** Whether this column is nullable (multiplicity lowerBound === 0). */
|
|
41
42
|
nullable?: boolean;
|
|
42
43
|
/** Physical relational type (e.g. 'VARCHAR(100)', 'DECIMAL(18,4)'). */
|
|
@@ -156,6 +157,34 @@ export const COVERAGE_NAME_SANDBOX = 'Legend-AI-Sandbox';
|
|
|
156
157
|
*/
|
|
157
158
|
export const TDS_SAMPLE_VALUES_DELIMITER = '-- e.g.';
|
|
158
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Splits a TDS column `doc` on the sample-values delimiter. Without the
|
|
162
|
+
* delimiter the whole string is the documentation; blank halves are omitted.
|
|
163
|
+
*/
|
|
164
|
+
export function parseTDSColumnDoc(doc: string): {
|
|
165
|
+
documentation?: string;
|
|
166
|
+
sampleValues?: string;
|
|
167
|
+
} {
|
|
168
|
+
const delimiterIndex = doc.indexOf(TDS_SAMPLE_VALUES_DELIMITER);
|
|
169
|
+
const result: { documentation?: string; sampleValues?: string } = {};
|
|
170
|
+
const documentation = (
|
|
171
|
+
delimiterIndex === -1 ? doc : doc.substring(0, delimiterIndex)
|
|
172
|
+
).trim();
|
|
173
|
+
if (documentation) {
|
|
174
|
+
result.documentation = documentation;
|
|
175
|
+
}
|
|
176
|
+
if (delimiterIndex === -1) {
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
const sampleValues = doc
|
|
180
|
+
.substring(delimiterIndex + TDS_SAMPLE_VALUES_DELIMITER.length)
|
|
181
|
+
.trim();
|
|
182
|
+
if (sampleValues) {
|
|
183
|
+
result.sampleValues = sampleValues;
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
159
188
|
export function getTodayISO(): string {
|
|
160
189
|
return new Date().toISOString().slice(0, 10);
|
|
161
190
|
}
|
|
@@ -165,6 +194,9 @@ export const LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID =
|
|
|
165
194
|
|
|
166
195
|
export const LEGEND_AI_ALTERNATE_ROOT_ACTION_ID = 'alternate-root';
|
|
167
196
|
|
|
197
|
+
/** Prefix of a Pure data-product accessor query — `#P{path}#->...`. */
|
|
198
|
+
export const DATA_PRODUCT_ACCESSOR_PREFIX = '#P{';
|
|
199
|
+
|
|
168
200
|
export const LEGEND_AI_FEEDBACK_PROMPT = 'Was this helpful?';
|
|
169
201
|
export const LEGEND_AI_DEFAULT_MODEL_LABEL = 'Auto';
|
|
170
202
|
|
|
@@ -204,6 +236,12 @@ export class LegendAIThinkingStep {
|
|
|
204
236
|
status!: LegendAIThinkingStepStatus;
|
|
205
237
|
}
|
|
206
238
|
|
|
239
|
+
export enum LegendAIPythonCodeStatus {
|
|
240
|
+
LOADING = 'loading',
|
|
241
|
+
READY = 'ready',
|
|
242
|
+
ERROR = 'error',
|
|
243
|
+
}
|
|
244
|
+
|
|
207
245
|
export enum LegendAIErrorType {
|
|
208
246
|
PERMISSION = 'PERMISSION',
|
|
209
247
|
NETWORK = 'NETWORK',
|
|
@@ -249,7 +287,13 @@ export class LegendAIUserMessage {
|
|
|
249
287
|
text!: string;
|
|
250
288
|
}
|
|
251
289
|
|
|
252
|
-
|
|
290
|
+
/** A prior SQL attempt carried into orchestrator (Pure) query generation. */
|
|
291
|
+
export interface LegendAIPriorSqlFailure {
|
|
292
|
+
failedSql?: string;
|
|
293
|
+
failedReason?: string;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface LegendAIFallbackAction extends LegendAIPriorSqlFailure {
|
|
253
297
|
label: string;
|
|
254
298
|
actionId: string;
|
|
255
299
|
resolvedEntities?: LegendAIResolvedEntities;
|
|
@@ -575,6 +619,7 @@ export interface LegendAIModelEntity {
|
|
|
575
619
|
properties: LegendAIModelProperty[];
|
|
576
620
|
isRootMapped?: boolean;
|
|
577
621
|
isQueryable?: boolean;
|
|
622
|
+
milestoning?: string;
|
|
578
623
|
}
|
|
579
624
|
|
|
580
625
|
export interface LegendAIModelAssociation {
|
|
@@ -618,12 +663,20 @@ export interface LegendAIPhysicalDataset {
|
|
|
618
663
|
table?: string;
|
|
619
664
|
}
|
|
620
665
|
|
|
666
|
+
export interface LegendAIFunctionInfo {
|
|
667
|
+
name: string;
|
|
668
|
+
functionPath: string;
|
|
669
|
+
returnType?: string;
|
|
670
|
+
parameters: LegendAIParameterInfo[];
|
|
671
|
+
}
|
|
672
|
+
|
|
621
673
|
export interface LegendAIModelContext {
|
|
622
674
|
entities: LegendAIModelEntity[];
|
|
623
675
|
associations: LegendAIModelAssociation[];
|
|
624
676
|
enumerations?: LegendAIEnumerationInfo[];
|
|
625
677
|
executables?: LegendAIExecutableInfo[];
|
|
626
678
|
datasets?: LegendAIPhysicalDataset[];
|
|
679
|
+
functions?: LegendAIFunctionInfo[];
|
|
627
680
|
dataspaceDescription?: string;
|
|
628
681
|
}
|
|
629
682
|
|
|
@@ -403,6 +403,7 @@ export abstract class LegendAI_LegendApplicationPlugin_Extension extends LegendA
|
|
|
403
403
|
metadata: LegendAIProductMetadata,
|
|
404
404
|
config: LegendAIConfig,
|
|
405
405
|
diagnosis?: string,
|
|
406
|
+
services?: TDSServiceSchema[],
|
|
406
407
|
): Promise<LegendAIResultAnalysis | undefined>;
|
|
407
408
|
|
|
408
409
|
/**
|
|
@@ -506,6 +507,7 @@ export abstract class LegendAI_LegendApplicationPlugin_Extension extends LegendA
|
|
|
506
507
|
_accessPoints: TDSServiceSchema[],
|
|
507
508
|
_dataProductCoordinates: LegendAIOrchestratorDataProductCoordinates,
|
|
508
509
|
_config: LegendAIConfig,
|
|
510
|
+
_question?: string,
|
|
509
511
|
): Promise<void> {
|
|
510
512
|
return Promise.resolve();
|
|
511
513
|
}
|