@superatomai/sdk-node 0.0.71 → 0.0.72
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/README.md +942 -942
- package/dist/index.d.mts +89 -42
- package/dist/index.d.ts +89 -42
- package/dist/index.js +1386 -827
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1386 -827
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1458,6 +1458,351 @@ var QueryCache = class {
|
|
|
1458
1458
|
};
|
|
1459
1459
|
var queryCache = new QueryCache();
|
|
1460
1460
|
|
|
1461
|
+
// src/userResponse/llm-result-truncator.ts
|
|
1462
|
+
var DEFAULT_MAX_ROWS = 10;
|
|
1463
|
+
var DEFAULT_MAX_CHARS_PER_FIELD = 500;
|
|
1464
|
+
function inferFieldType(value) {
|
|
1465
|
+
if (value === null || value === void 0) {
|
|
1466
|
+
return "null";
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof value === "string") {
|
|
1469
|
+
if (isDateString(value)) {
|
|
1470
|
+
return "date";
|
|
1471
|
+
}
|
|
1472
|
+
return "string";
|
|
1473
|
+
}
|
|
1474
|
+
if (typeof value === "number") {
|
|
1475
|
+
return "number";
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof value === "boolean") {
|
|
1478
|
+
return "boolean";
|
|
1479
|
+
}
|
|
1480
|
+
if (Array.isArray(value)) {
|
|
1481
|
+
return "array";
|
|
1482
|
+
}
|
|
1483
|
+
if (typeof value === "object") {
|
|
1484
|
+
return "object";
|
|
1485
|
+
}
|
|
1486
|
+
return "unknown";
|
|
1487
|
+
}
|
|
1488
|
+
function isDateString(value) {
|
|
1489
|
+
const datePatterns = [
|
|
1490
|
+
/^\d{4}-\d{2}-\d{2}$/,
|
|
1491
|
+
// YYYY-MM-DD
|
|
1492
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
|
|
1493
|
+
// ISO 8601
|
|
1494
|
+
/^\d{2}\/\d{2}\/\d{4}$/,
|
|
1495
|
+
// MM/DD/YYYY
|
|
1496
|
+
/^\d{4}\/\d{2}\/\d{2}$/
|
|
1497
|
+
// YYYY/MM/DD
|
|
1498
|
+
];
|
|
1499
|
+
return datePatterns.some((pattern) => pattern.test(value));
|
|
1500
|
+
}
|
|
1501
|
+
function truncateTextField(value, maxLength) {
|
|
1502
|
+
if (value.length <= maxLength) {
|
|
1503
|
+
return { text: value, wasTruncated: false };
|
|
1504
|
+
}
|
|
1505
|
+
const truncated = value.substring(0, maxLength);
|
|
1506
|
+
const remaining = value.length - maxLength;
|
|
1507
|
+
return {
|
|
1508
|
+
text: `${truncated}... (${remaining} more chars)`,
|
|
1509
|
+
wasTruncated: true
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
function truncateFieldValue(value, maxCharsPerField) {
|
|
1513
|
+
if (value === null || value === void 0) {
|
|
1514
|
+
return { value, wasTruncated: false };
|
|
1515
|
+
}
|
|
1516
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1517
|
+
return { value, wasTruncated: false };
|
|
1518
|
+
}
|
|
1519
|
+
if (typeof value === "string") {
|
|
1520
|
+
const result2 = truncateTextField(value, maxCharsPerField);
|
|
1521
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1522
|
+
}
|
|
1523
|
+
if (Array.isArray(value)) {
|
|
1524
|
+
if (value.length === 0) {
|
|
1525
|
+
return { value: [], wasTruncated: false };
|
|
1526
|
+
}
|
|
1527
|
+
const preview = value.slice(0, 3);
|
|
1528
|
+
const hasMore = value.length > 3;
|
|
1529
|
+
return {
|
|
1530
|
+
value: hasMore ? `[${preview.join(", ")}... (${value.length} items)]` : value,
|
|
1531
|
+
wasTruncated: hasMore
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
if (typeof value === "object") {
|
|
1535
|
+
const jsonStr = JSON.stringify(value);
|
|
1536
|
+
const result2 = truncateTextField(jsonStr, maxCharsPerField);
|
|
1537
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1538
|
+
}
|
|
1539
|
+
const strValue = String(value);
|
|
1540
|
+
const result = truncateTextField(strValue, maxCharsPerField);
|
|
1541
|
+
return { value: result.text, wasTruncated: result.wasTruncated };
|
|
1542
|
+
}
|
|
1543
|
+
function truncateRow(row, maxCharsPerField) {
|
|
1544
|
+
const truncatedRow = {};
|
|
1545
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1546
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1547
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1548
|
+
truncatedRow[key] = result.value;
|
|
1549
|
+
if (result.wasTruncated) {
|
|
1550
|
+
truncatedFields.add(key);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return { row: truncatedRow, truncatedFields };
|
|
1554
|
+
}
|
|
1555
|
+
function extractMetadataFromObject(obj, dataKey, maxCharsPerField) {
|
|
1556
|
+
const metadata = {};
|
|
1557
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1558
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1559
|
+
if (key === dataKey) {
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (Array.isArray(value)) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1566
|
+
metadata[key] = result.value;
|
|
1567
|
+
if (result.wasTruncated) {
|
|
1568
|
+
truncatedFields.add(key);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return { metadata, truncatedFields };
|
|
1572
|
+
}
|
|
1573
|
+
function extractSchema(data, truncatedFields = /* @__PURE__ */ new Set()) {
|
|
1574
|
+
if (!data || data.length === 0) {
|
|
1575
|
+
return [];
|
|
1576
|
+
}
|
|
1577
|
+
const firstRow = data[0];
|
|
1578
|
+
const schema2 = [];
|
|
1579
|
+
for (const [name, value] of Object.entries(firstRow)) {
|
|
1580
|
+
schema2.push({
|
|
1581
|
+
name,
|
|
1582
|
+
type: inferFieldType(value),
|
|
1583
|
+
truncated: truncatedFields.has(name) ? true : void 0
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
return schema2;
|
|
1587
|
+
}
|
|
1588
|
+
function truncateDataArray(data, options = {}) {
|
|
1589
|
+
const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1590
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1591
|
+
if (!data || !Array.isArray(data)) {
|
|
1592
|
+
return {
|
|
1593
|
+
data: [],
|
|
1594
|
+
totalRecords: 0,
|
|
1595
|
+
recordsShown: 0,
|
|
1596
|
+
truncatedFields: /* @__PURE__ */ new Set()
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
const totalRecords = data.length;
|
|
1600
|
+
const rowsToProcess = data.slice(0, maxRows);
|
|
1601
|
+
const truncatedData = [];
|
|
1602
|
+
const allTruncatedFields = /* @__PURE__ */ new Set();
|
|
1603
|
+
for (const row of rowsToProcess) {
|
|
1604
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(row, maxCharsPerField);
|
|
1605
|
+
truncatedData.push(truncatedRow);
|
|
1606
|
+
for (const field of truncatedFields) {
|
|
1607
|
+
allTruncatedFields.add(field);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return {
|
|
1611
|
+
data: truncatedData,
|
|
1612
|
+
totalRecords,
|
|
1613
|
+
recordsShown: truncatedData.length,
|
|
1614
|
+
truncatedFields: allTruncatedFields
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function buildTruncationNote(totalRecords, recordsShown, truncatedFields, maxCharsPerField, sourceName) {
|
|
1618
|
+
const parts = [];
|
|
1619
|
+
if (totalRecords > recordsShown) {
|
|
1620
|
+
const source = sourceName ? ` from ${sourceName}` : "";
|
|
1621
|
+
parts.push(`Showing ${recordsShown} of ${totalRecords} total records${source}`);
|
|
1622
|
+
}
|
|
1623
|
+
if (truncatedFields.size > 0) {
|
|
1624
|
+
const fieldList = Array.from(truncatedFields).join(", ");
|
|
1625
|
+
parts.push(`Fields truncated to ${maxCharsPerField} chars: ${fieldList}`);
|
|
1626
|
+
}
|
|
1627
|
+
return parts.length > 0 ? parts.join(". ") + "." : null;
|
|
1628
|
+
}
|
|
1629
|
+
function formatQueryResultForLLM(data, options = {}) {
|
|
1630
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1631
|
+
if (!Array.isArray(data)) {
|
|
1632
|
+
if (data !== null && data !== void 0) {
|
|
1633
|
+
return {
|
|
1634
|
+
summary: {
|
|
1635
|
+
totalRecords: 1,
|
|
1636
|
+
recordsShown: 1,
|
|
1637
|
+
schema: [{ name: "result", type: inferFieldType(data) }]
|
|
1638
|
+
},
|
|
1639
|
+
data: [{ result: data }],
|
|
1640
|
+
truncationNote: null
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
return {
|
|
1644
|
+
summary: {
|
|
1645
|
+
totalRecords: 0,
|
|
1646
|
+
recordsShown: 0,
|
|
1647
|
+
schema: []
|
|
1648
|
+
},
|
|
1649
|
+
data: [],
|
|
1650
|
+
truncationNote: null
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
const {
|
|
1654
|
+
data: truncatedData,
|
|
1655
|
+
totalRecords,
|
|
1656
|
+
recordsShown,
|
|
1657
|
+
truncatedFields
|
|
1658
|
+
} = truncateDataArray(data, options);
|
|
1659
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1660
|
+
const truncationNote = buildTruncationNote(
|
|
1661
|
+
totalRecords,
|
|
1662
|
+
recordsShown,
|
|
1663
|
+
truncatedFields,
|
|
1664
|
+
maxCharsPerField,
|
|
1665
|
+
"query"
|
|
1666
|
+
);
|
|
1667
|
+
return {
|
|
1668
|
+
summary: {
|
|
1669
|
+
totalRecords,
|
|
1670
|
+
recordsShown,
|
|
1671
|
+
schema: schema2
|
|
1672
|
+
},
|
|
1673
|
+
data: truncatedData,
|
|
1674
|
+
truncationNote
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
function formatToolResultForLLM(result, options = {}) {
|
|
1678
|
+
const { toolName, toolLimit } = options;
|
|
1679
|
+
const effectiveMaxRows = toolLimit ?? options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1680
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1681
|
+
if (result === null || result === void 0) {
|
|
1682
|
+
return {
|
|
1683
|
+
toolName,
|
|
1684
|
+
summary: {
|
|
1685
|
+
totalRecords: 0,
|
|
1686
|
+
recordsShown: 0,
|
|
1687
|
+
schema: []
|
|
1688
|
+
},
|
|
1689
|
+
data: [],
|
|
1690
|
+
truncationNote: null
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
if (typeof result === "string") {
|
|
1694
|
+
const { text, wasTruncated } = truncateTextField(result, maxCharsPerField);
|
|
1695
|
+
return {
|
|
1696
|
+
toolName,
|
|
1697
|
+
summary: {
|
|
1698
|
+
totalRecords: 1,
|
|
1699
|
+
recordsShown: 1,
|
|
1700
|
+
schema: [{ name: "result", type: "string", truncated: wasTruncated || void 0 }]
|
|
1701
|
+
},
|
|
1702
|
+
data: [{ result: text }],
|
|
1703
|
+
truncationNote: wasTruncated ? `Result truncated to ${maxCharsPerField} chars.` : null
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
if (Array.isArray(result)) {
|
|
1707
|
+
const {
|
|
1708
|
+
data: truncatedData,
|
|
1709
|
+
totalRecords,
|
|
1710
|
+
recordsShown,
|
|
1711
|
+
truncatedFields
|
|
1712
|
+
} = truncateDataArray(result, {
|
|
1713
|
+
maxRows: effectiveMaxRows,
|
|
1714
|
+
maxCharsPerField
|
|
1715
|
+
});
|
|
1716
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1717
|
+
const truncationNote = buildTruncationNote(
|
|
1718
|
+
totalRecords,
|
|
1719
|
+
recordsShown,
|
|
1720
|
+
truncatedFields,
|
|
1721
|
+
maxCharsPerField,
|
|
1722
|
+
toolName
|
|
1723
|
+
);
|
|
1724
|
+
return {
|
|
1725
|
+
toolName,
|
|
1726
|
+
summary: {
|
|
1727
|
+
totalRecords,
|
|
1728
|
+
recordsShown,
|
|
1729
|
+
schema: schema2
|
|
1730
|
+
},
|
|
1731
|
+
data: truncatedData,
|
|
1732
|
+
truncationNote
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
if (typeof result === "object") {
|
|
1736
|
+
const objResult = result;
|
|
1737
|
+
const dataWrapperKeys = ["data", "results", "items", "records", "rows", "list"];
|
|
1738
|
+
for (const key of dataWrapperKeys) {
|
|
1739
|
+
if (Array.isArray(objResult[key])) {
|
|
1740
|
+
const innerData = objResult[key];
|
|
1741
|
+
const {
|
|
1742
|
+
data: truncatedData,
|
|
1743
|
+
totalRecords,
|
|
1744
|
+
recordsShown,
|
|
1745
|
+
truncatedFields: dataTruncatedFields
|
|
1746
|
+
} = truncateDataArray(innerData, {
|
|
1747
|
+
maxRows: effectiveMaxRows,
|
|
1748
|
+
maxCharsPerField
|
|
1749
|
+
});
|
|
1750
|
+
const {
|
|
1751
|
+
metadata,
|
|
1752
|
+
truncatedFields: metadataTruncatedFields
|
|
1753
|
+
} = extractMetadataFromObject(objResult, key, maxCharsPerField);
|
|
1754
|
+
const allTruncatedFields = /* @__PURE__ */ new Set([...dataTruncatedFields, ...metadataTruncatedFields]);
|
|
1755
|
+
const schema3 = extractSchema(truncatedData, dataTruncatedFields);
|
|
1756
|
+
const truncationNote2 = buildTruncationNote(
|
|
1757
|
+
totalRecords,
|
|
1758
|
+
recordsShown,
|
|
1759
|
+
allTruncatedFields,
|
|
1760
|
+
maxCharsPerField,
|
|
1761
|
+
toolName
|
|
1762
|
+
);
|
|
1763
|
+
const hasMetadata = Object.keys(metadata).length > 0;
|
|
1764
|
+
return {
|
|
1765
|
+
toolName,
|
|
1766
|
+
summary: {
|
|
1767
|
+
totalRecords,
|
|
1768
|
+
recordsShown,
|
|
1769
|
+
schema: schema3
|
|
1770
|
+
},
|
|
1771
|
+
...hasMetadata && { metadata },
|
|
1772
|
+
data: truncatedData,
|
|
1773
|
+
truncationNote: truncationNote2
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(objResult, maxCharsPerField);
|
|
1778
|
+
const schema2 = extractSchema([truncatedRow], truncatedFields);
|
|
1779
|
+
const truncationNote = truncatedFields.size > 0 ? `Fields truncated to ${maxCharsPerField} chars: ${Array.from(truncatedFields).join(", ")}.` : null;
|
|
1780
|
+
return {
|
|
1781
|
+
toolName,
|
|
1782
|
+
summary: {
|
|
1783
|
+
totalRecords: 1,
|
|
1784
|
+
recordsShown: 1,
|
|
1785
|
+
schema: schema2
|
|
1786
|
+
},
|
|
1787
|
+
data: [truncatedRow],
|
|
1788
|
+
truncationNote
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
toolName,
|
|
1793
|
+
summary: {
|
|
1794
|
+
totalRecords: 1,
|
|
1795
|
+
recordsShown: 1,
|
|
1796
|
+
schema: [{ name: "result", type: inferFieldType(result) }]
|
|
1797
|
+
},
|
|
1798
|
+
data: [{ result }],
|
|
1799
|
+
truncationNote: null
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function formatResultAsString(formattedResult) {
|
|
1803
|
+
return JSON.stringify(formattedResult, null, 2);
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1461
1806
|
// src/handlers/data-request.ts
|
|
1462
1807
|
function getQueryCacheKey(query) {
|
|
1463
1808
|
if (typeof query === "string") {
|
|
@@ -1539,15 +1884,25 @@ async function handleDataRequest(data, collections, sendMessage) {
|
|
|
1539
1884
|
}
|
|
1540
1885
|
}
|
|
1541
1886
|
if (uiBlock) {
|
|
1887
|
+
const formattedResult = formatToolResultForLLM(result, {
|
|
1888
|
+
maxRows: 3,
|
|
1889
|
+
// Only need a few sample rows for summary
|
|
1890
|
+
maxCharsPerField: 100
|
|
1891
|
+
// Short truncation for summary
|
|
1892
|
+
});
|
|
1542
1893
|
const dataSummary = {
|
|
1543
1894
|
_dataReceived: true,
|
|
1544
1895
|
_timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1545
1896
|
_collection: collection,
|
|
1546
1897
|
_operation: op,
|
|
1547
|
-
|
|
1898
|
+
_totalRecords: formattedResult.summary.totalRecords,
|
|
1899
|
+
_recordsShown: formattedResult.summary.recordsShown,
|
|
1900
|
+
_metadata: formattedResult.metadata,
|
|
1901
|
+
// Preserve totalItems, totalDeadstockItems, etc.
|
|
1902
|
+
_schema: formattedResult.summary.schema
|
|
1548
1903
|
};
|
|
1549
1904
|
uiBlock.setComponentData(dataSummary);
|
|
1550
|
-
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (
|
|
1905
|
+
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (${formattedResult.summary.totalRecords} total records)`);
|
|
1551
1906
|
} else {
|
|
1552
1907
|
logger.warn(`UIBlock ${uiBlockId} not found in threads`);
|
|
1553
1908
|
}
|
|
@@ -5964,97 +6319,335 @@ var ConversationSearch = {
|
|
|
5964
6319
|
};
|
|
5965
6320
|
var conversation_search_default = ConversationSearch;
|
|
5966
6321
|
|
|
5967
|
-
// src/userResponse/
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
5972
|
-
this.defaultLimit = config?.defaultLimit || 10;
|
|
5973
|
-
this.apiKey = config?.apiKey;
|
|
5974
|
-
this.modelStrategy = config?.modelStrategy || "fast";
|
|
5975
|
-
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
|
|
6322
|
+
// src/userResponse/prompt-extractor.ts
|
|
6323
|
+
function extractPromptText(content) {
|
|
6324
|
+
if (content === null || content === void 0) {
|
|
6325
|
+
return "";
|
|
5976
6326
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
6327
|
+
if (typeof content === "string") {
|
|
6328
|
+
return content;
|
|
6329
|
+
}
|
|
6330
|
+
if (Array.isArray(content)) {
|
|
6331
|
+
return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
|
|
6332
|
+
}
|
|
6333
|
+
if (content && typeof content === "object") {
|
|
6334
|
+
return extractObjectText(content);
|
|
6335
|
+
}
|
|
6336
|
+
return String(content);
|
|
6337
|
+
}
|
|
6338
|
+
function extractContentBlockText(item) {
|
|
6339
|
+
if (typeof item === "string") {
|
|
6340
|
+
return item;
|
|
6341
|
+
}
|
|
6342
|
+
if (item && typeof item === "object") {
|
|
6343
|
+
const obj = item;
|
|
6344
|
+
if (typeof obj.text === "string") {
|
|
6345
|
+
return obj.text;
|
|
5991
6346
|
}
|
|
6347
|
+
if (typeof obj.content === "string") {
|
|
6348
|
+
return obj.content;
|
|
6349
|
+
}
|
|
6350
|
+
return JSON.stringify(item, null, 2);
|
|
6351
|
+
}
|
|
6352
|
+
return String(item);
|
|
6353
|
+
}
|
|
6354
|
+
function extractObjectText(obj) {
|
|
6355
|
+
if (typeof obj.text === "string") {
|
|
6356
|
+
return obj.text;
|
|
6357
|
+
}
|
|
6358
|
+
if (typeof obj.content === "string") {
|
|
6359
|
+
return obj.content;
|
|
6360
|
+
}
|
|
6361
|
+
return JSON.stringify(obj, null, 2);
|
|
6362
|
+
}
|
|
6363
|
+
|
|
6364
|
+
// src/userResponse/constants.ts
|
|
6365
|
+
var MAX_QUERY_VALIDATION_RETRIES = 3;
|
|
6366
|
+
var MAX_QUERY_ATTEMPTS = 6;
|
|
6367
|
+
var MAX_TOOL_ATTEMPTS = 3;
|
|
6368
|
+
var STREAM_FLUSH_INTERVAL_MS = 50;
|
|
6369
|
+
var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
|
|
6370
|
+
var STREAM_DELAY_MS = 50;
|
|
6371
|
+
var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
|
|
6372
|
+
var MAX_TOKENS_QUERY_FIX = 2048;
|
|
6373
|
+
var MAX_TOKENS_COMPONENT_MATCHING = 8192;
|
|
6374
|
+
var MAX_TOKENS_CLASSIFICATION = 1500;
|
|
6375
|
+
var MAX_TOKENS_ADAPTATION = 8192;
|
|
6376
|
+
var MAX_TOKENS_TEXT_RESPONSE = 4e3;
|
|
6377
|
+
var MAX_TOKENS_NEXT_QUESTIONS = 1200;
|
|
6378
|
+
var DEFAULT_MAX_ROWS_FOR_LLM = 10;
|
|
6379
|
+
var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
|
|
6380
|
+
var STREAM_PREVIEW_MAX_ROWS = 10;
|
|
6381
|
+
var STREAM_PREVIEW_MAX_CHARS = 200;
|
|
6382
|
+
var TOOL_TRACKING_MAX_ROWS = 5;
|
|
6383
|
+
var TOOL_TRACKING_MAX_CHARS = 200;
|
|
6384
|
+
var TOOL_TRACKING_SAMPLE_ROWS = 3;
|
|
6385
|
+
var MAX_COMPONENT_QUERY_LIMIT = 10;
|
|
6386
|
+
var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
|
|
6387
|
+
var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
|
|
6388
|
+
var MAX_TOOL_CALLING_ITERATIONS = 20;
|
|
6389
|
+
var KNOWLEDGE_BASE_TOP_K = 3;
|
|
6390
|
+
|
|
6391
|
+
// src/userResponse/stream-buffer.ts
|
|
6392
|
+
var StreamBuffer = class {
|
|
6393
|
+
constructor(callback) {
|
|
6394
|
+
this.buffer = "";
|
|
6395
|
+
this.flushTimer = null;
|
|
6396
|
+
this.fullText = "";
|
|
6397
|
+
this.callback = callback;
|
|
5992
6398
|
}
|
|
5993
6399
|
/**
|
|
5994
|
-
*
|
|
5995
|
-
* @param strategy - 'best', 'fast', or 'balanced'
|
|
6400
|
+
* Check if the buffer has a callback configured
|
|
5996
6401
|
*/
|
|
5997
|
-
|
|
5998
|
-
this.
|
|
5999
|
-
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
6402
|
+
hasCallback() {
|
|
6403
|
+
return !!this.callback;
|
|
6000
6404
|
}
|
|
6001
6405
|
/**
|
|
6002
|
-
* Get
|
|
6003
|
-
* @returns The current model strategy
|
|
6406
|
+
* Get all text that has been written (including already flushed)
|
|
6004
6407
|
*/
|
|
6005
|
-
|
|
6006
|
-
return this.
|
|
6408
|
+
getFullText() {
|
|
6409
|
+
return this.fullText;
|
|
6007
6410
|
}
|
|
6008
6411
|
/**
|
|
6009
|
-
*
|
|
6010
|
-
*
|
|
6412
|
+
* Write a chunk to the buffer
|
|
6413
|
+
* Large chunks or chunks with newlines are flushed immediately
|
|
6414
|
+
* Small chunks are batched and flushed after a short interval
|
|
6415
|
+
*
|
|
6416
|
+
* @param chunk - Text chunk to write
|
|
6011
6417
|
*/
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
this.conversationSimilarityThreshold = 0.8;
|
|
6418
|
+
write(chunk) {
|
|
6419
|
+
this.fullText += chunk;
|
|
6420
|
+
if (!this.callback) {
|
|
6016
6421
|
return;
|
|
6017
6422
|
}
|
|
6018
|
-
this.
|
|
6019
|
-
|
|
6423
|
+
this.buffer += chunk;
|
|
6424
|
+
if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
|
|
6425
|
+
this.flushNow();
|
|
6426
|
+
} else if (!this.flushTimer) {
|
|
6427
|
+
this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
|
|
6428
|
+
}
|
|
6020
6429
|
}
|
|
6021
6430
|
/**
|
|
6022
|
-
*
|
|
6023
|
-
*
|
|
6431
|
+
* Flush the buffer immediately
|
|
6432
|
+
* Call this before tool execution or other operations that need clean output
|
|
6024
6433
|
*/
|
|
6025
|
-
|
|
6026
|
-
|
|
6434
|
+
flush() {
|
|
6435
|
+
this.flushNow();
|
|
6027
6436
|
}
|
|
6028
6437
|
/**
|
|
6029
|
-
*
|
|
6438
|
+
* Internal flush implementation
|
|
6030
6439
|
*/
|
|
6031
|
-
|
|
6032
|
-
|
|
6440
|
+
flushNow() {
|
|
6441
|
+
if (this.flushTimer) {
|
|
6442
|
+
clearTimeout(this.flushTimer);
|
|
6443
|
+
this.flushTimer = null;
|
|
6444
|
+
}
|
|
6445
|
+
if (this.buffer && this.callback) {
|
|
6446
|
+
this.callback(this.buffer);
|
|
6447
|
+
this.buffer = "";
|
|
6448
|
+
}
|
|
6033
6449
|
}
|
|
6034
6450
|
/**
|
|
6035
|
-
*
|
|
6036
|
-
*
|
|
6037
|
-
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
6451
|
+
* Clean up resources
|
|
6452
|
+
* Call this when done with the buffer
|
|
6038
6453
|
*/
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6454
|
+
dispose() {
|
|
6455
|
+
this.flush();
|
|
6456
|
+
this.callback = void 0;
|
|
6457
|
+
}
|
|
6458
|
+
};
|
|
6459
|
+
function streamDelay(ms = STREAM_DELAY_MS) {
|
|
6460
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6461
|
+
}
|
|
6462
|
+
async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
|
|
6463
|
+
if (!streamBuffer.hasCallback()) {
|
|
6464
|
+
return operation();
|
|
6465
|
+
}
|
|
6466
|
+
const startTime = Date.now();
|
|
6467
|
+
await streamDelay(30);
|
|
6468
|
+
streamBuffer.write(`\u23F3 ${progressMessage}`);
|
|
6469
|
+
const heartbeatInterval = setInterval(() => {
|
|
6470
|
+
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
6471
|
+
if (elapsedSeconds >= 1) {
|
|
6472
|
+
streamBuffer.write(` (${elapsedSeconds}s)`);
|
|
6473
|
+
}
|
|
6474
|
+
}, intervalMs);
|
|
6475
|
+
try {
|
|
6476
|
+
const result = await operation();
|
|
6477
|
+
return result;
|
|
6478
|
+
} finally {
|
|
6479
|
+
clearInterval(heartbeatInterval);
|
|
6480
|
+
streamBuffer.write("\n\n");
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
// src/userResponse/utils/component-props-processor.ts
|
|
6485
|
+
var NUMERIC_CONFIG_KEYS = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6486
|
+
var STRING_CONFIG_KEYS = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6487
|
+
var CONFIG_FIELDS_TO_VALIDATE = [
|
|
6488
|
+
"xAxisKey",
|
|
6489
|
+
"yAxisKey",
|
|
6490
|
+
"valueKey",
|
|
6491
|
+
"nameKey",
|
|
6492
|
+
"labelKey",
|
|
6493
|
+
"groupBy",
|
|
6494
|
+
"aggregationField",
|
|
6495
|
+
"seriesKey",
|
|
6496
|
+
"sizeKey",
|
|
6497
|
+
"xAggregationField",
|
|
6498
|
+
"yAggregationField"
|
|
6499
|
+
];
|
|
6500
|
+
function findMatchingField(fieldName, configKey, validFieldNames, fieldTypes, providerName) {
|
|
6501
|
+
if (!fieldName) return null;
|
|
6502
|
+
const lowerField = fieldName.toLowerCase();
|
|
6503
|
+
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6504
|
+
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6505
|
+
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6506
|
+
const containsMatches = validFieldNames.filter(
|
|
6507
|
+
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6508
|
+
);
|
|
6509
|
+
if (containsMatches.length === 1) return containsMatches[0];
|
|
6510
|
+
if (NUMERIC_CONFIG_KEYS.includes(configKey)) {
|
|
6511
|
+
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6512
|
+
const match = numericFields.find(
|
|
6513
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6514
|
+
);
|
|
6515
|
+
if (match) return match;
|
|
6516
|
+
if (numericFields.length > 0) {
|
|
6517
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6518
|
+
return numericFields[0];
|
|
6043
6519
|
}
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6520
|
+
}
|
|
6521
|
+
if (STRING_CONFIG_KEYS.includes(configKey)) {
|
|
6522
|
+
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6523
|
+
const match = stringFields.find(
|
|
6524
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6525
|
+
);
|
|
6526
|
+
if (match) return match;
|
|
6527
|
+
if (stringFields.length > 0) {
|
|
6528
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6529
|
+
return stringFields[0];
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6533
|
+
return validFieldNames[0];
|
|
6534
|
+
}
|
|
6535
|
+
function validateConfigFieldNames(config, outputSchema, providerName) {
|
|
6536
|
+
if (!outputSchema?.fields || !config) return config;
|
|
6537
|
+
const validFieldNames = outputSchema.fields.map((f) => f.name);
|
|
6538
|
+
const fieldTypes = outputSchema.fields.reduce((acc, f) => {
|
|
6539
|
+
acc[f.name] = f.type;
|
|
6540
|
+
return acc;
|
|
6541
|
+
}, {});
|
|
6542
|
+
const correctedConfig = { ...config };
|
|
6543
|
+
for (const configKey of CONFIG_FIELDS_TO_VALIDATE) {
|
|
6544
|
+
const fieldValue = correctedConfig[configKey];
|
|
6545
|
+
if (fieldValue && typeof fieldValue === "string") {
|
|
6546
|
+
if (!validFieldNames.includes(fieldValue)) {
|
|
6547
|
+
const correctedField = findMatchingField(fieldValue, configKey, validFieldNames, fieldTypes, providerName);
|
|
6548
|
+
if (correctedField) {
|
|
6549
|
+
logger.warn(`[${providerName}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6550
|
+
correctedConfig[configKey] = correctedField;
|
|
6049
6551
|
}
|
|
6050
6552
|
}
|
|
6051
6553
|
}
|
|
6052
|
-
|
|
6554
|
+
}
|
|
6555
|
+
if (Array.isArray(correctedConfig.series)) {
|
|
6556
|
+
correctedConfig.series = correctedConfig.series.map((s) => {
|
|
6557
|
+
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6558
|
+
const correctedField = findMatchingField(s.dataKey, "yAxisKey", validFieldNames, fieldTypes, providerName);
|
|
6559
|
+
if (correctedField) {
|
|
6560
|
+
logger.warn(`[${providerName}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6561
|
+
return { ...s, dataKey: correctedField };
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
return s;
|
|
6565
|
+
});
|
|
6566
|
+
}
|
|
6567
|
+
return correctedConfig;
|
|
6568
|
+
}
|
|
6569
|
+
function validateExternalTool(externalTool, executedTools, providerName) {
|
|
6570
|
+
if (!externalTool) {
|
|
6571
|
+
return { valid: true };
|
|
6572
|
+
}
|
|
6573
|
+
const toolId = externalTool.toolId;
|
|
6574
|
+
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
6575
|
+
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
6576
|
+
if (!isValidTool) {
|
|
6577
|
+
logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
6578
|
+
return { valid: false };
|
|
6579
|
+
}
|
|
6580
|
+
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6581
|
+
return { valid: true, executedTool };
|
|
6582
|
+
}
|
|
6583
|
+
function validateAndCleanQuery(query, config) {
|
|
6584
|
+
if (!query) {
|
|
6585
|
+
return { query: null, wasModified: false };
|
|
6586
|
+
}
|
|
6587
|
+
let wasModified = false;
|
|
6588
|
+
let cleanedQuery = query;
|
|
6589
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6590
|
+
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6591
|
+
logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6592
|
+
return { query: null, wasModified: true };
|
|
6593
|
+
}
|
|
6594
|
+
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6595
|
+
if (fixed) {
|
|
6596
|
+
logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6597
|
+
wasModified = true;
|
|
6598
|
+
if (typeof cleanedQuery === "string") {
|
|
6599
|
+
cleanedQuery = fixedQuery;
|
|
6600
|
+
} else if (cleanedQuery?.sql) {
|
|
6601
|
+
cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6604
|
+
if (typeof cleanedQuery === "string") {
|
|
6605
|
+
const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6606
|
+
if (limitedQuery !== cleanedQuery) wasModified = true;
|
|
6607
|
+
cleanedQuery = limitedQuery;
|
|
6608
|
+
} else if (cleanedQuery?.sql) {
|
|
6609
|
+
const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6610
|
+
if (limitedSql !== cleanedQuery.sql) wasModified = true;
|
|
6611
|
+
cleanedQuery = { ...cleanedQuery, sql: limitedSql };
|
|
6612
|
+
}
|
|
6613
|
+
return { query: cleanedQuery, wasModified };
|
|
6614
|
+
}
|
|
6615
|
+
function processComponentProps(props, executedTools, config) {
|
|
6616
|
+
let cleanedProps = { ...props };
|
|
6617
|
+
if (cleanedProps.externalTool) {
|
|
6618
|
+
const { valid, executedTool } = validateExternalTool(
|
|
6619
|
+
cleanedProps.externalTool,
|
|
6620
|
+
executedTools,
|
|
6621
|
+
config.providerName
|
|
6622
|
+
);
|
|
6623
|
+
if (!valid) {
|
|
6624
|
+
cleanedProps.externalTool = null;
|
|
6625
|
+
} else if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6626
|
+
cleanedProps.config = validateConfigFieldNames(
|
|
6627
|
+
cleanedProps.config,
|
|
6628
|
+
executedTool.outputSchema,
|
|
6629
|
+
config.providerName
|
|
6630
|
+
);
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
if (cleanedProps.query) {
|
|
6634
|
+
const { query } = validateAndCleanQuery(cleanedProps.query, config);
|
|
6635
|
+
cleanedProps.query = query;
|
|
6636
|
+
}
|
|
6637
|
+
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6638
|
+
logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
6639
|
+
}
|
|
6640
|
+
return cleanedProps;
|
|
6641
|
+
}
|
|
6642
|
+
|
|
6643
|
+
// src/userResponse/services/query-execution-service.ts
|
|
6644
|
+
var QueryExecutionService = class {
|
|
6645
|
+
constructor(config) {
|
|
6646
|
+
this.config = config;
|
|
6053
6647
|
}
|
|
6054
6648
|
/**
|
|
6055
|
-
* Get the cache key for a query
|
|
6649
|
+
* Get the cache key for a query
|
|
6056
6650
|
* This ensures the cache key matches what the frontend will send
|
|
6057
|
-
* Used for both caching and internal deduplication
|
|
6058
6651
|
*/
|
|
6059
6652
|
getQueryCacheKey(query) {
|
|
6060
6653
|
if (typeof query === "string") {
|
|
@@ -6070,17 +6663,19 @@ var BaseLLM = class {
|
|
|
6070
6663
|
return "";
|
|
6071
6664
|
}
|
|
6072
6665
|
/**
|
|
6073
|
-
* Execute a query against the database
|
|
6666
|
+
* Execute a query against the database
|
|
6074
6667
|
* @param query - The SQL query to execute (string or object with sql/values)
|
|
6075
6668
|
* @param collections - Collections object containing database execute function
|
|
6076
6669
|
* @returns Object with result data and cache key
|
|
6077
|
-
* @throws Error if query execution fails
|
|
6078
6670
|
*/
|
|
6079
|
-
async
|
|
6671
|
+
async executeQuery(query, collections) {
|
|
6080
6672
|
const cacheKey = this.getQueryCacheKey(query);
|
|
6081
6673
|
if (!cacheKey) {
|
|
6082
6674
|
throw new Error("Invalid query format: expected string or object with sql property");
|
|
6083
6675
|
}
|
|
6676
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
6677
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
6678
|
+
}
|
|
6084
6679
|
const result = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6085
6680
|
return { result, cacheKey };
|
|
6086
6681
|
}
|
|
@@ -6088,7 +6683,7 @@ var BaseLLM = class {
|
|
|
6088
6683
|
* Request the LLM to fix a failed SQL query
|
|
6089
6684
|
* @param failedQuery - The query that failed execution
|
|
6090
6685
|
* @param errorMessage - The error message from the failed execution
|
|
6091
|
-
* @param componentContext - Context about the component
|
|
6686
|
+
* @param componentContext - Context about the component
|
|
6092
6687
|
* @param apiKey - Optional API key
|
|
6093
6688
|
* @returns Fixed query string
|
|
6094
6689
|
*/
|
|
@@ -6129,10 +6724,10 @@ Fixed SQL query:`;
|
|
|
6129
6724
|
user: prompt
|
|
6130
6725
|
},
|
|
6131
6726
|
{
|
|
6132
|
-
model: this.getModelForTask("simple"),
|
|
6133
|
-
maxTokens:
|
|
6727
|
+
model: this.config.getModelForTask("simple"),
|
|
6728
|
+
maxTokens: MAX_TOKENS_QUERY_FIX,
|
|
6134
6729
|
temperature: 0,
|
|
6135
|
-
apiKey: this.getApiKey(apiKey)
|
|
6730
|
+
apiKey: this.config.getApiKey(apiKey)
|
|
6136
6731
|
}
|
|
6137
6732
|
);
|
|
6138
6733
|
let fixedQuery = response.trim();
|
|
@@ -6142,81 +6737,656 @@ Fixed SQL query:`;
|
|
|
6142
6737
|
return validatedQuery;
|
|
6143
6738
|
}
|
|
6144
6739
|
/**
|
|
6145
|
-
*
|
|
6146
|
-
*
|
|
6147
|
-
*
|
|
6148
|
-
*
|
|
6149
|
-
* @param
|
|
6150
|
-
* @
|
|
6151
|
-
* @param apiKey - Optional API key
|
|
6152
|
-
* @param logCollector - Optional log collector
|
|
6153
|
-
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
6154
|
-
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
6740
|
+
* Validate a single component's query with retry logic
|
|
6741
|
+
* @param component - The component to validate
|
|
6742
|
+
* @param collections - Collections object containing database execute function
|
|
6743
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6744
|
+
* @param logCollector - Optional log collector for logging
|
|
6745
|
+
* @returns Validation result with component, query key, and result
|
|
6155
6746
|
*/
|
|
6156
|
-
async
|
|
6157
|
-
const
|
|
6158
|
-
const
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
logger.
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6747
|
+
async validateSingleQuery(component, collections, apiKey, logCollector) {
|
|
6748
|
+
const query = component.props?.query;
|
|
6749
|
+
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6750
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6751
|
+
let finalQueryKey = originalQueryKey;
|
|
6752
|
+
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6753
|
+
let currentQueryStr = queryStr;
|
|
6754
|
+
let validated = false;
|
|
6755
|
+
let lastError = "";
|
|
6756
|
+
let result = null;
|
|
6757
|
+
let attempts = 0;
|
|
6758
|
+
logger.info(`[${this.config.providerName}] Validating query for component: ${component.name} (${component.type})`);
|
|
6759
|
+
while (attempts < MAX_QUERY_VALIDATION_RETRIES && !validated) {
|
|
6760
|
+
attempts++;
|
|
6761
|
+
try {
|
|
6762
|
+
logger.debug(`[${this.config.providerName}] Query validation attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES} for ${component.name}`);
|
|
6763
|
+
const validationResult = await this.executeQuery(currentQuery, collections);
|
|
6764
|
+
result = validationResult.result;
|
|
6765
|
+
validated = true;
|
|
6766
|
+
queryCache.set(validationResult.cacheKey, result);
|
|
6767
|
+
logger.info(`[${this.config.providerName}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6768
|
+
logCollector?.info(`\u2713 Query validated for ${component.name}`);
|
|
6769
|
+
if (currentQueryStr !== queryStr) {
|
|
6770
|
+
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6771
|
+
component.props = {
|
|
6772
|
+
...component.props,
|
|
6773
|
+
query: fixedQuery
|
|
6774
|
+
};
|
|
6775
|
+
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6776
|
+
logger.info(`[${this.config.providerName}] Updated ${component.name} with fixed query`);
|
|
6777
|
+
}
|
|
6778
|
+
} catch (error) {
|
|
6779
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
6780
|
+
logger.warn(`[${this.config.providerName}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES}): ${lastError}`);
|
|
6781
|
+
logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
|
|
6782
|
+
if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
|
|
6783
|
+
logger.error(`[${this.config.providerName}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6784
|
+
logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
|
|
6785
|
+
break;
|
|
6786
|
+
}
|
|
6787
|
+
logger.info(`[${this.config.providerName}] Requesting query fix from LLM for ${component.name}...`);
|
|
6788
|
+
logCollector?.info(`Requesting query fix for ${component.name}...`);
|
|
6789
|
+
try {
|
|
6790
|
+
const fixedQueryStr = await this.requestQueryFix(
|
|
6791
|
+
currentQueryStr,
|
|
6792
|
+
lastError,
|
|
6793
|
+
{
|
|
6794
|
+
name: component.name,
|
|
6795
|
+
type: component.type,
|
|
6796
|
+
title: component.props?.title
|
|
6797
|
+
},
|
|
6798
|
+
apiKey
|
|
6799
|
+
);
|
|
6800
|
+
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6801
|
+
logger.info(`[${this.config.providerName}] Received fixed query for ${component.name}, retrying...`);
|
|
6802
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6803
|
+
currentQueryStr = limitedFixedQuery;
|
|
6804
|
+
if (typeof currentQuery === "string") {
|
|
6805
|
+
currentQuery = limitedFixedQuery;
|
|
6806
|
+
} else {
|
|
6807
|
+
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6808
|
+
}
|
|
6809
|
+
} else {
|
|
6810
|
+
logger.warn(`[${this.config.providerName}] LLM returned same or empty query, stopping retries`);
|
|
6811
|
+
break;
|
|
6812
|
+
}
|
|
6813
|
+
} catch (fixError) {
|
|
6814
|
+
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6815
|
+
logger.error(`[${this.config.providerName}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6816
|
+
break;
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6820
|
+
if (!validated) {
|
|
6821
|
+
logger.warn(`[${this.config.providerName}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6822
|
+
logCollector?.warn(`Component ${component.name} excluded from response`);
|
|
6823
|
+
}
|
|
6824
|
+
return {
|
|
6825
|
+
component: validated ? component : null,
|
|
6826
|
+
queryKey: finalQueryKey,
|
|
6827
|
+
result,
|
|
6828
|
+
validated
|
|
6829
|
+
};
|
|
6830
|
+
}
|
|
6831
|
+
/**
|
|
6832
|
+
* Validate multiple component queries in parallel
|
|
6833
|
+
* @param components - Array of components with potential queries
|
|
6834
|
+
* @param collections - Collections object containing database execute function
|
|
6835
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6836
|
+
* @param logCollector - Optional log collector for logging
|
|
6837
|
+
* @returns Object with validated components and query results map
|
|
6838
|
+
*/
|
|
6839
|
+
async validateComponentQueries(components, collections, apiKey, logCollector) {
|
|
6840
|
+
const queryResults = /* @__PURE__ */ new Map();
|
|
6841
|
+
const validatedComponents = [];
|
|
6842
|
+
const componentsWithoutQuery = [];
|
|
6843
|
+
const componentsWithQuery = [];
|
|
6844
|
+
for (const component of components) {
|
|
6845
|
+
if (!component.props?.query) {
|
|
6846
|
+
componentsWithoutQuery.push(component);
|
|
6847
|
+
} else {
|
|
6848
|
+
componentsWithQuery.push(component);
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
validatedComponents.push(...componentsWithoutQuery);
|
|
6852
|
+
if (componentsWithQuery.length === 0) {
|
|
6853
|
+
return { components: validatedComponents, queryResults };
|
|
6854
|
+
}
|
|
6855
|
+
logger.info(`[${this.config.providerName}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6856
|
+
logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6857
|
+
const validationPromises = componentsWithQuery.map(
|
|
6858
|
+
(component) => this.validateSingleQuery(component, collections, apiKey, logCollector)
|
|
6859
|
+
);
|
|
6860
|
+
const results = await Promise.allSettled(validationPromises);
|
|
6861
|
+
for (let i = 0; i < results.length; i++) {
|
|
6862
|
+
const result = results[i];
|
|
6863
|
+
const component = componentsWithQuery[i];
|
|
6864
|
+
if (result.status === "fulfilled") {
|
|
6865
|
+
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6866
|
+
if (validated && validatedComponent) {
|
|
6867
|
+
validatedComponents.push(validatedComponent);
|
|
6868
|
+
if (queryResult) {
|
|
6869
|
+
queryResults.set(queryKey, queryResult);
|
|
6870
|
+
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
} else {
|
|
6874
|
+
logger.error(`[${this.config.providerName}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6875
|
+
logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
logger.info(`[${this.config.providerName}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6879
|
+
return {
|
|
6880
|
+
components: validatedComponents,
|
|
6881
|
+
queryResults
|
|
6882
|
+
};
|
|
6883
|
+
}
|
|
6884
|
+
};
|
|
6885
|
+
|
|
6886
|
+
// src/userResponse/services/tool-executor-service.ts
|
|
6887
|
+
var ToolExecutorService = class {
|
|
6888
|
+
constructor(config) {
|
|
6889
|
+
this.queryAttempts = /* @__PURE__ */ new Map();
|
|
6890
|
+
this.toolAttempts = /* @__PURE__ */ new Map();
|
|
6891
|
+
this.executedToolsList = [];
|
|
6892
|
+
this.maxAttemptsReached = false;
|
|
6893
|
+
this.config = config;
|
|
6894
|
+
}
|
|
6895
|
+
/**
|
|
6896
|
+
* Reset state for a new execution
|
|
6897
|
+
*/
|
|
6898
|
+
reset() {
|
|
6899
|
+
this.queryAttempts.clear();
|
|
6900
|
+
this.toolAttempts.clear();
|
|
6901
|
+
this.executedToolsList = [];
|
|
6902
|
+
this.maxAttemptsReached = false;
|
|
6903
|
+
}
|
|
6904
|
+
/**
|
|
6905
|
+
* Get list of successfully executed tools
|
|
6906
|
+
*/
|
|
6907
|
+
getExecutedTools() {
|
|
6908
|
+
return this.executedToolsList;
|
|
6909
|
+
}
|
|
6910
|
+
/**
|
|
6911
|
+
* Check if max attempts were reached
|
|
6912
|
+
*/
|
|
6913
|
+
isMaxAttemptsReached() {
|
|
6914
|
+
return this.maxAttemptsReached;
|
|
6915
|
+
}
|
|
6916
|
+
/**
|
|
6917
|
+
* Create a tool handler function for LLM.streamWithTools
|
|
6918
|
+
* @param externalTools - List of available external tools
|
|
6919
|
+
* @returns Tool handler function
|
|
6920
|
+
*/
|
|
6921
|
+
createToolHandler(externalTools) {
|
|
6922
|
+
return async (toolName, toolInput) => {
|
|
6923
|
+
if (toolName === "execute_query") {
|
|
6924
|
+
return this.executeQuery(toolInput);
|
|
6925
|
+
} else {
|
|
6926
|
+
return this.executeExternalTool(toolName, toolInput, externalTools);
|
|
6927
|
+
}
|
|
6928
|
+
};
|
|
6929
|
+
}
|
|
6930
|
+
/**
|
|
6931
|
+
* Execute a SQL query with retry tracking and streaming feedback
|
|
6932
|
+
*/
|
|
6933
|
+
async executeQuery(toolInput) {
|
|
6934
|
+
let sql = toolInput.sql;
|
|
6935
|
+
const params = toolInput.params || {};
|
|
6936
|
+
const reasoning = toolInput.reasoning;
|
|
6937
|
+
const { streamBuffer, collections, logCollector, providerName } = this.config;
|
|
6938
|
+
sql = ensureQueryLimit(sql, MAX_COMPONENT_QUERY_LIMIT, MAX_COMPONENT_QUERY_LIMIT);
|
|
6939
|
+
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
6940
|
+
const attempts = (this.queryAttempts.get(queryKey) || 0) + 1;
|
|
6941
|
+
this.queryAttempts.set(queryKey, attempts);
|
|
6942
|
+
logger.info(`[${providerName}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
|
|
6943
|
+
if (Object.keys(params).length > 0) {
|
|
6944
|
+
logger.info(`[${providerName}] Query params: ${JSON.stringify(params)}`);
|
|
6945
|
+
}
|
|
6946
|
+
if (reasoning) {
|
|
6947
|
+
logCollector?.info(`Query reasoning: ${reasoning}`);
|
|
6948
|
+
}
|
|
6949
|
+
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
6950
|
+
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
6951
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
6952
|
+
logCollector?.error(errorMsg);
|
|
6953
|
+
this.maxAttemptsReached = true;
|
|
6954
|
+
if (streamBuffer.hasCallback()) {
|
|
6955
|
+
streamBuffer.write(`
|
|
6956
|
+
|
|
6957
|
+
\u274C ${errorMsg}
|
|
6958
|
+
|
|
6959
|
+
Please try rephrasing your question or simplifying your request.
|
|
6960
|
+
|
|
6961
|
+
`);
|
|
6962
|
+
}
|
|
6963
|
+
throw new Error(errorMsg);
|
|
6964
|
+
}
|
|
6965
|
+
try {
|
|
6966
|
+
streamBuffer.flush();
|
|
6967
|
+
if (streamBuffer.hasCallback()) {
|
|
6968
|
+
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
6969
|
+
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
6970
|
+
if (attempts === 1) {
|
|
6971
|
+
streamBuffer.write(`
|
|
6972
|
+
|
|
6973
|
+
\u{1F50D} **Analyzing your question...**
|
|
6974
|
+
|
|
6975
|
+
`);
|
|
6976
|
+
await streamDelay();
|
|
6977
|
+
if (reasoning) {
|
|
6978
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
6979
|
+
|
|
6980
|
+
`);
|
|
6981
|
+
await streamDelay();
|
|
6982
|
+
}
|
|
6983
|
+
streamBuffer.write(`\u{1F4DD} **Generated SQL Query:**
|
|
6984
|
+
\`\`\`sql
|
|
6985
|
+
${sql}
|
|
6986
|
+
\`\`\`${paramsDisplay}
|
|
6987
|
+
|
|
6988
|
+
`);
|
|
6989
|
+
await streamDelay();
|
|
6990
|
+
} else {
|
|
6991
|
+
streamBuffer.write(`
|
|
6992
|
+
|
|
6993
|
+
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
6994
|
+
|
|
6995
|
+
`);
|
|
6996
|
+
await streamDelay();
|
|
6997
|
+
if (reasoning) {
|
|
6998
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
6999
|
+
|
|
7000
|
+
`);
|
|
7001
|
+
await streamDelay();
|
|
7002
|
+
}
|
|
7003
|
+
streamBuffer.write(`\u{1F4DD} **Corrected SQL Query:**
|
|
7004
|
+
\`\`\`sql
|
|
7005
|
+
${sql}
|
|
7006
|
+
\`\`\`${paramsDisplay}
|
|
7007
|
+
|
|
7008
|
+
`);
|
|
7009
|
+
await streamDelay();
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
logCollector?.logQuery?.(
|
|
7013
|
+
`Executing SQL query (attempt ${attempts})`,
|
|
7014
|
+
{ sql, params },
|
|
7015
|
+
{ reasoning, attempt: attempts }
|
|
7016
|
+
);
|
|
7017
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
7018
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
7019
|
+
}
|
|
7020
|
+
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
7021
|
+
const result = await withProgressHeartbeat(
|
|
7022
|
+
() => collections["database"]["execute"](queryPayload),
|
|
7023
|
+
"Executing database query",
|
|
7024
|
+
streamBuffer
|
|
7025
|
+
);
|
|
7026
|
+
const data = result?.data || result;
|
|
7027
|
+
const rowCount = result?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7028
|
+
logger.info(`[${providerName}] Query executed successfully, rows returned: ${rowCount}`);
|
|
7029
|
+
logCollector?.info(`Query successful, returned ${rowCount} rows`);
|
|
7030
|
+
if (streamBuffer.hasCallback()) {
|
|
7031
|
+
streamBuffer.write(`
|
|
7032
|
+
\u2705 **Query executed successfully!**
|
|
7033
|
+
|
|
7034
|
+
`);
|
|
7035
|
+
await streamDelay();
|
|
7036
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
7037
|
+
const firstRow = data[0];
|
|
7038
|
+
const columns = Object.keys(firstRow);
|
|
7039
|
+
if (data.length === 1 && columns.length === 1) {
|
|
7040
|
+
const value = firstRow[columns[0]];
|
|
7041
|
+
streamBuffer.write(`**Result:** ${value}
|
|
7042
|
+
|
|
7043
|
+
`);
|
|
7044
|
+
await streamDelay();
|
|
7045
|
+
} else if (data.length > 0) {
|
|
7046
|
+
streamBuffer.write(`**Retrieved ${rowCount} rows**
|
|
7047
|
+
|
|
7048
|
+
`);
|
|
7049
|
+
await streamDelay();
|
|
7050
|
+
const streamPreview = formatQueryResultForLLM(data, {
|
|
7051
|
+
maxRows: STREAM_PREVIEW_MAX_ROWS,
|
|
7052
|
+
maxCharsPerField: STREAM_PREVIEW_MAX_CHARS
|
|
7053
|
+
});
|
|
7054
|
+
streamBuffer.write(`<DataTable>${JSON.stringify(streamPreview.data)}</DataTable>
|
|
7055
|
+
|
|
7056
|
+
`);
|
|
7057
|
+
if (streamPreview.truncationNote) {
|
|
7058
|
+
streamBuffer.write(`*${streamPreview.truncationNote}*
|
|
7059
|
+
|
|
7060
|
+
`);
|
|
7061
|
+
}
|
|
7062
|
+
await streamDelay();
|
|
7063
|
+
}
|
|
7064
|
+
} else if (Array.isArray(data) && data.length === 0) {
|
|
7065
|
+
streamBuffer.write(`**No rows returned.**
|
|
7066
|
+
|
|
7067
|
+
`);
|
|
7068
|
+
await streamDelay();
|
|
7069
|
+
}
|
|
7070
|
+
streamBuffer.write(`\u{1F4CA} **Analyzing results...**
|
|
7071
|
+
|
|
7072
|
+
`);
|
|
7073
|
+
}
|
|
7074
|
+
const formattedResult = formatQueryResultForLLM(data, {
|
|
7075
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7076
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7077
|
+
});
|
|
7078
|
+
logger.info(`[${providerName}] Query result formatted: ${formattedResult.summary.recordsShown}/${formattedResult.summary.totalRecords} records`);
|
|
7079
|
+
if (formattedResult.truncationNote) {
|
|
7080
|
+
logger.info(`[${providerName}] Truncation: ${formattedResult.truncationNote}`);
|
|
7081
|
+
}
|
|
7082
|
+
return formatResultAsString(formattedResult);
|
|
7083
|
+
} catch (error) {
|
|
7084
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7085
|
+
logger.error(`[${providerName}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7086
|
+
logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7087
|
+
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7088
|
+
if (streamBuffer.hasCallback()) {
|
|
7089
|
+
streamBuffer.write(`\u274C **Query execution failed:**
|
|
7090
|
+
\`\`\`
|
|
7091
|
+
${errorMsg}
|
|
7092
|
+
\`\`\`
|
|
7093
|
+
|
|
7094
|
+
`);
|
|
7095
|
+
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7096
|
+
streamBuffer.write(`\u{1F527} **Generating corrected query...**
|
|
7097
|
+
|
|
7098
|
+
`);
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
/**
|
|
7105
|
+
* Execute an external tool with retry tracking and streaming feedback
|
|
7106
|
+
*/
|
|
7107
|
+
async executeExternalTool(toolName, toolInput, externalTools) {
|
|
7108
|
+
const { streamBuffer, logCollector, providerName } = this.config;
|
|
7109
|
+
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7110
|
+
if (!externalTool) {
|
|
7111
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
7112
|
+
}
|
|
7113
|
+
const attempts = (this.toolAttempts.get(toolName) || 0) + 1;
|
|
7114
|
+
this.toolAttempts.set(toolName, attempts);
|
|
7115
|
+
logger.info(`[${providerName}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
|
|
7116
|
+
logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
|
|
7117
|
+
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7118
|
+
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7119
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
7120
|
+
logCollector?.error(errorMsg);
|
|
7121
|
+
if (streamBuffer.hasCallback()) {
|
|
7122
|
+
streamBuffer.write(`
|
|
7123
|
+
|
|
7124
|
+
\u274C ${errorMsg}
|
|
7125
|
+
|
|
7126
|
+
Please try rephrasing your request or contact support.
|
|
7127
|
+
|
|
7128
|
+
`);
|
|
7129
|
+
}
|
|
7130
|
+
throw new Error(errorMsg);
|
|
7131
|
+
}
|
|
7132
|
+
try {
|
|
7133
|
+
streamBuffer.flush();
|
|
7134
|
+
if (streamBuffer.hasCallback()) {
|
|
7135
|
+
if (attempts === 1) {
|
|
7136
|
+
streamBuffer.write(`
|
|
7137
|
+
|
|
7138
|
+
\u{1F517} **Executing ${externalTool.name}...**
|
|
7139
|
+
|
|
7140
|
+
`);
|
|
7141
|
+
} else {
|
|
7142
|
+
streamBuffer.write(`
|
|
7143
|
+
|
|
7144
|
+
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7145
|
+
|
|
7146
|
+
`);
|
|
7147
|
+
}
|
|
7148
|
+
await streamDelay();
|
|
7149
|
+
}
|
|
7150
|
+
const result = await withProgressHeartbeat(
|
|
7151
|
+
() => externalTool.fn(toolInput),
|
|
7152
|
+
`Running ${externalTool.name}`,
|
|
7153
|
+
streamBuffer
|
|
7154
|
+
);
|
|
7155
|
+
logger.info(`[${providerName}] External tool ${externalTool.name} executed successfully`);
|
|
7156
|
+
logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
|
|
7157
|
+
if (!this.executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7158
|
+
const formattedForTracking = formatToolResultForLLM(result, {
|
|
7159
|
+
toolName: externalTool.name,
|
|
7160
|
+
toolLimit: externalTool.limit,
|
|
7161
|
+
maxRows: TOOL_TRACKING_MAX_ROWS,
|
|
7162
|
+
maxCharsPerField: TOOL_TRACKING_MAX_CHARS
|
|
7163
|
+
});
|
|
7164
|
+
this.executedToolsList.push({
|
|
7165
|
+
id: externalTool.id,
|
|
7166
|
+
name: externalTool.name,
|
|
7167
|
+
params: toolInput,
|
|
7168
|
+
result: {
|
|
7169
|
+
_totalRecords: formattedForTracking.summary.totalRecords,
|
|
7170
|
+
_recordsShown: formattedForTracking.summary.recordsShown,
|
|
7171
|
+
_metadata: formattedForTracking.metadata,
|
|
7172
|
+
_sampleData: formattedForTracking.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS)
|
|
7173
|
+
},
|
|
7174
|
+
outputSchema: externalTool.outputSchema
|
|
7175
|
+
});
|
|
7176
|
+
logger.info(`[${providerName}] Tracked executed tool: ${externalTool.name} with ${formattedForTracking.summary.totalRecords} total records`);
|
|
7177
|
+
}
|
|
7178
|
+
if (streamBuffer.hasCallback()) {
|
|
7179
|
+
streamBuffer.write(`\u2705 **${externalTool.name} completed successfully**
|
|
7180
|
+
|
|
7181
|
+
`);
|
|
7182
|
+
await streamDelay();
|
|
7183
|
+
}
|
|
7184
|
+
const formattedToolResult = formatToolResultForLLM(result, {
|
|
7185
|
+
toolName: externalTool.name,
|
|
7186
|
+
toolLimit: externalTool.limit,
|
|
7187
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7188
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7189
|
+
});
|
|
7190
|
+
logger.info(`[${providerName}] Tool result formatted: ${formattedToolResult.summary.recordsShown}/${formattedToolResult.summary.totalRecords} records`);
|
|
7191
|
+
if (formattedToolResult.truncationNote) {
|
|
7192
|
+
logger.info(`[${providerName}] Truncation: ${formattedToolResult.truncationNote}`);
|
|
7193
|
+
}
|
|
7194
|
+
return formatResultAsString(formattedToolResult);
|
|
7195
|
+
} catch (error) {
|
|
7196
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7197
|
+
logger.error(`[${providerName}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7198
|
+
logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
|
|
7199
|
+
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7200
|
+
if (streamBuffer.hasCallback()) {
|
|
7201
|
+
streamBuffer.write(`\u274C **${externalTool.name} failed:**
|
|
7202
|
+
\`\`\`
|
|
7203
|
+
${errorMsg}
|
|
7204
|
+
\`\`\`
|
|
7205
|
+
|
|
7206
|
+
`);
|
|
7207
|
+
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7208
|
+
streamBuffer.write(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7209
|
+
|
|
7210
|
+
`);
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
};
|
|
7217
|
+
|
|
7218
|
+
// src/userResponse/base-llm.ts
|
|
7219
|
+
var BaseLLM = class {
|
|
7220
|
+
constructor(config) {
|
|
7221
|
+
this.model = config?.model || this.getDefaultModel();
|
|
7222
|
+
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
7223
|
+
this.defaultLimit = config?.defaultLimit || 10;
|
|
7224
|
+
this.apiKey = config?.apiKey;
|
|
7225
|
+
this.modelStrategy = config?.modelStrategy || "fast";
|
|
7226
|
+
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7227
|
+
this.queryService = new QueryExecutionService({
|
|
7228
|
+
defaultLimit: this.defaultLimit,
|
|
7229
|
+
getModelForTask: (taskType) => this.getModelForTask(taskType),
|
|
7230
|
+
getApiKey: (apiKey) => this.getApiKey(apiKey),
|
|
7231
|
+
providerName: this.getProviderName()
|
|
7232
|
+
});
|
|
7233
|
+
}
|
|
7234
|
+
/**
|
|
7235
|
+
* Get the appropriate model based on task type and model strategy
|
|
7236
|
+
* @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
|
|
7237
|
+
* @returns The model string to use for this task
|
|
7238
|
+
*/
|
|
7239
|
+
getModelForTask(taskType) {
|
|
7240
|
+
switch (this.modelStrategy) {
|
|
7241
|
+
case "best":
|
|
7242
|
+
return this.model;
|
|
7243
|
+
case "fast":
|
|
7244
|
+
return this.fastModel;
|
|
7245
|
+
case "balanced":
|
|
7246
|
+
default:
|
|
7247
|
+
return taskType === "complex" ? this.model : this.fastModel;
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
/**
|
|
7251
|
+
* Set the model strategy at runtime
|
|
7252
|
+
* @param strategy - 'best', 'fast', or 'balanced'
|
|
7253
|
+
*/
|
|
7254
|
+
setModelStrategy(strategy) {
|
|
7255
|
+
this.modelStrategy = strategy;
|
|
7256
|
+
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
7257
|
+
}
|
|
7258
|
+
/**
|
|
7259
|
+
* Get the current model strategy
|
|
7260
|
+
* @returns The current model strategy
|
|
7261
|
+
*/
|
|
7262
|
+
getModelStrategy() {
|
|
7263
|
+
return this.modelStrategy;
|
|
7264
|
+
}
|
|
7265
|
+
/**
|
|
7266
|
+
* Set the conversation similarity threshold at runtime
|
|
7267
|
+
* @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
|
|
7268
|
+
*/
|
|
7269
|
+
setConversationSimilarityThreshold(threshold) {
|
|
7270
|
+
if (threshold < 0 || threshold > 1) {
|
|
7271
|
+
logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default ${DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD}`);
|
|
7272
|
+
this.conversationSimilarityThreshold = DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7273
|
+
return;
|
|
7274
|
+
}
|
|
7275
|
+
this.conversationSimilarityThreshold = threshold;
|
|
7276
|
+
logger.info(`[${this.getProviderName()}] Conversation similarity threshold set to: ${threshold}`);
|
|
7277
|
+
}
|
|
7278
|
+
/**
|
|
7279
|
+
* Get the current conversation similarity threshold
|
|
7280
|
+
* @returns The current threshold value
|
|
7281
|
+
*/
|
|
7282
|
+
getConversationSimilarityThreshold() {
|
|
7283
|
+
return this.conversationSimilarityThreshold;
|
|
7284
|
+
}
|
|
7285
|
+
/**
|
|
7286
|
+
* Get the API key (from instance, parameter, or environment)
|
|
7287
|
+
*/
|
|
7288
|
+
getApiKey(apiKey) {
|
|
7289
|
+
return apiKey || this.apiKey || this.getDefaultApiKey();
|
|
7290
|
+
}
|
|
7291
|
+
/**
|
|
7292
|
+
* Check if a component contains a Form (data_modification component)
|
|
7293
|
+
* Forms have hardcoded defaultValues that become stale when cached
|
|
7294
|
+
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
7295
|
+
*/
|
|
7296
|
+
containsFormComponent(component) {
|
|
7297
|
+
if (!component) return false;
|
|
7298
|
+
if (component.type === "Form" || component.name === "DynamicForm") {
|
|
7299
|
+
return true;
|
|
7300
|
+
}
|
|
7301
|
+
if (component.type === "Container" || component.name === "MultiComponentContainer") {
|
|
7302
|
+
const nestedComponents = component.props?.config?.components || [];
|
|
7303
|
+
for (const nested of nestedComponents) {
|
|
7304
|
+
if (nested.type === "Form" || nested.name === "DynamicForm") {
|
|
7305
|
+
return true;
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
return false;
|
|
7310
|
+
}
|
|
7311
|
+
/**
|
|
7312
|
+
* Match components from text response suggestions and generate follow-up questions
|
|
7313
|
+
* Takes a text response with component suggestions (c1:type format) and matches with available components
|
|
7314
|
+
* Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
|
|
7315
|
+
* All components are placed in a default MultiComponentContainer layout
|
|
7316
|
+
* @param analysisContent - The text response containing component suggestions
|
|
7317
|
+
* @param components - List of available components
|
|
7318
|
+
* @param apiKey - Optional API key
|
|
7319
|
+
* @param logCollector - Optional log collector
|
|
7320
|
+
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
7321
|
+
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
7322
|
+
*/
|
|
7323
|
+
async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, logCollector, componentStreamCallback, deferredTools, executedTools, collections, userId) {
|
|
7324
|
+
const methodStartTime = Date.now();
|
|
7325
|
+
const methodName = "matchComponentsFromAnalysis";
|
|
7326
|
+
logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
|
|
7327
|
+
try {
|
|
7328
|
+
logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
|
|
7329
|
+
let availableComponentsText = "No components available";
|
|
7330
|
+
if (components && components.length > 0) {
|
|
7331
|
+
availableComponentsText = components.map((comp, idx) => {
|
|
7332
|
+
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
7333
|
+
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
7334
|
+
return `${idx + 1}. ID: ${comp.id}
|
|
7335
|
+
Name: ${comp.name}
|
|
7336
|
+
Type: ${comp.type}
|
|
7337
|
+
Description: ${comp.description || "No description"}
|
|
7338
|
+
Keywords: ${keywords}
|
|
7339
|
+
Props Structure: ${propsPreview}`;
|
|
7340
|
+
}).join("\n\n");
|
|
7341
|
+
}
|
|
7342
|
+
let deferredToolsText = "No deferred external tools for this request.";
|
|
7343
|
+
if (deferredTools && deferredTools.length > 0) {
|
|
7344
|
+
logger.info(`[${this.getProviderName()}] Passing ${deferredTools.length} deferred tools to component matching`);
|
|
7345
|
+
deferredToolsText = "The following external tools need user input via a Form component.\n**IMPORTANT: Use these EXACT values when generating Form externalTool prop.**\n\n" + deferredTools.map((tool, idx) => {
|
|
7346
|
+
return `${idx + 1}. **${tool.name}**
|
|
7347
|
+
toolId: "${tool.id}" (USE THIS EXACT VALUE - do not modify!)
|
|
7348
|
+
toolName: "${tool.name}"
|
|
7349
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7350
|
+
requiredFields:
|
|
7351
|
+
${JSON.stringify(tool.requiredFields || [], null, 2)}`;
|
|
7352
|
+
}).join("\n\n");
|
|
7353
|
+
}
|
|
7354
|
+
let executedToolsText = "No external tools were executed for data fetching.";
|
|
7355
|
+
if (executedTools && executedTools.length > 0) {
|
|
7356
|
+
logger.info(`[${this.getProviderName()}] Passing ${executedTools.length} executed tools to component matching`);
|
|
7357
|
+
executedToolsText = "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
|
|
7358
|
+
let outputSchemaText = "Not available";
|
|
7359
|
+
let fieldNamesList = "";
|
|
7360
|
+
const recordCount = tool.result?._totalRecords ?? "unknown";
|
|
7361
|
+
let metadataText = "";
|
|
7362
|
+
if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
|
|
7363
|
+
const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
7364
|
+
metadataText = `
|
|
7365
|
+
\u{1F4CB} METADATA: ${metadataEntries}`;
|
|
7366
|
+
}
|
|
7367
|
+
if (tool.outputSchema) {
|
|
7368
|
+
const fields = tool.outputSchema.fields || [];
|
|
7369
|
+
const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
|
|
7370
|
+
const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
|
|
7371
|
+
fieldNamesList = `
|
|
7372
|
+
\u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
|
|
7373
|
+
\u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
|
|
7374
|
+
const fieldsText = fields.map(
|
|
7375
|
+
(f) => ` "${f.name}" (${f.type}): ${f.description}`
|
|
7376
|
+
).join("\n");
|
|
7377
|
+
outputSchemaText = `${tool.outputSchema.description}
|
|
7378
|
+
Fields:
|
|
7379
|
+
${fieldsText}`;
|
|
7380
|
+
}
|
|
7381
|
+
return `${idx + 1}. **${tool.name}**
|
|
7382
|
+
toolId: "${tool.id}"
|
|
7383
|
+
toolName: "${tool.name}"
|
|
7384
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7385
|
+
recordCount: ${recordCount} rows returned${metadataText}
|
|
7386
|
+
outputSchema: ${outputSchemaText}${fieldNamesList}`;
|
|
7387
|
+
}).join("\n\n");
|
|
7388
|
+
}
|
|
7389
|
+
const schemaDoc = schema.generateSchemaDocumentation();
|
|
6220
7390
|
const databaseRules = await promptLoader.loadDatabaseRules();
|
|
6221
7391
|
let knowledgeBaseContext = "No additional knowledge base context available.";
|
|
6222
7392
|
if (collections) {
|
|
@@ -6224,7 +7394,7 @@ ${fieldsText}`;
|
|
|
6224
7394
|
prompt: userPrompt || analysisContent,
|
|
6225
7395
|
collections,
|
|
6226
7396
|
userId,
|
|
6227
|
-
topK:
|
|
7397
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
6228
7398
|
});
|
|
6229
7399
|
knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
|
|
6230
7400
|
}
|
|
@@ -6240,22 +7410,6 @@ ${fieldsText}`;
|
|
|
6240
7410
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6241
7411
|
});
|
|
6242
7412
|
logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
|
|
6243
|
-
const extractPromptText = (content) => {
|
|
6244
|
-
if (typeof content === "string") return content;
|
|
6245
|
-
if (Array.isArray(content)) {
|
|
6246
|
-
return content.map((item) => {
|
|
6247
|
-
if (typeof item === "string") return item;
|
|
6248
|
-
if (item && typeof item.text === "string") return item.text;
|
|
6249
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
6250
|
-
return JSON.stringify(item, null, 2);
|
|
6251
|
-
}).join("\n\n---\n\n");
|
|
6252
|
-
}
|
|
6253
|
-
if (content && typeof content === "object") {
|
|
6254
|
-
if (typeof content.text === "string") return content.text;
|
|
6255
|
-
return JSON.stringify(content, null, 2);
|
|
6256
|
-
}
|
|
6257
|
-
return String(content);
|
|
6258
|
-
};
|
|
6259
7413
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "system", extractPromptText(prompts.system));
|
|
6260
7414
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "user", `Text Analysis:
|
|
6261
7415
|
${analysisContent}
|
|
@@ -6332,23 +7486,32 @@ ${executedToolsText}`);
|
|
|
6332
7486
|
{ componentName: answerComponent.name, componentType: answerComponent.type, reasoning: answerComponentData.reasoning }
|
|
6333
7487
|
);
|
|
6334
7488
|
}
|
|
6335
|
-
|
|
7489
|
+
let answerQuery = answerComponent.props?.query;
|
|
6336
7490
|
logger.info(`[${this.getProviderName()}] Answer component detected: ${answerComponent.name} (${answerComponent.type}), hasQuery: ${!!answerQuery}, hasDbExecute: ${!!collections?.["database"]?.["execute"]}`);
|
|
7491
|
+
if (answerQuery) {
|
|
7492
|
+
if (typeof answerQuery === "string") {
|
|
7493
|
+
answerQuery = ensureQueryLimit(answerQuery, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7494
|
+
} else if (answerQuery?.sql) {
|
|
7495
|
+
const queryObj = answerQuery;
|
|
7496
|
+
answerQuery = { ...queryObj, sql: ensureQueryLimit(queryObj.sql, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT) };
|
|
7497
|
+
}
|
|
7498
|
+
answerComponent.props.query = answerQuery;
|
|
7499
|
+
}
|
|
6337
7500
|
if (answerQuery && collections?.["database"]?.["execute"]) {
|
|
6338
7501
|
(async () => {
|
|
6339
|
-
const
|
|
7502
|
+
const maxRetries = MAX_QUERY_VALIDATION_RETRIES;
|
|
6340
7503
|
let attempts = 0;
|
|
6341
7504
|
let validated = false;
|
|
6342
7505
|
let currentQuery = answerQuery;
|
|
6343
7506
|
let currentQueryStr = typeof answerQuery === "string" ? answerQuery : answerQuery?.sql || "";
|
|
6344
7507
|
let lastError = "";
|
|
6345
7508
|
logger.info(`[${this.getProviderName()}] Validating answer component query before streaming...`);
|
|
6346
|
-
while (attempts <
|
|
7509
|
+
while (attempts < maxRetries && !validated) {
|
|
6347
7510
|
attempts++;
|
|
6348
7511
|
try {
|
|
6349
|
-
const cacheKey = this.getQueryCacheKey(currentQuery);
|
|
7512
|
+
const cacheKey = this.queryService.getQueryCacheKey(currentQuery);
|
|
6350
7513
|
if (cacheKey) {
|
|
6351
|
-
logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${
|
|
7514
|
+
logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${maxRetries}`);
|
|
6352
7515
|
const result2 = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6353
7516
|
queryCache.set(cacheKey, result2);
|
|
6354
7517
|
validated = true;
|
|
@@ -6363,11 +7526,11 @@ ${executedToolsText}`);
|
|
|
6363
7526
|
}
|
|
6364
7527
|
} catch (validationError) {
|
|
6365
7528
|
lastError = validationError instanceof Error ? validationError.message : String(validationError);
|
|
6366
|
-
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${
|
|
6367
|
-
if (attempts <
|
|
7529
|
+
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${maxRetries}): ${lastError}`);
|
|
7530
|
+
if (attempts < maxRetries) {
|
|
6368
7531
|
try {
|
|
6369
7532
|
logger.info(`[${this.getProviderName()}] Requesting LLM to fix answer component query...`);
|
|
6370
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
7533
|
+
const fixedQueryStr = await this.queryService.requestQueryFix(
|
|
6371
7534
|
currentQueryStr,
|
|
6372
7535
|
lastError,
|
|
6373
7536
|
{
|
|
@@ -6377,7 +7540,7 @@ ${executedToolsText}`);
|
|
|
6377
7540
|
},
|
|
6378
7541
|
apiKey
|
|
6379
7542
|
);
|
|
6380
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit,
|
|
7543
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6381
7544
|
if (typeof currentQuery === "string") {
|
|
6382
7545
|
currentQuery = limitedFixedQuery;
|
|
6383
7546
|
} else {
|
|
@@ -6418,7 +7581,7 @@ ${executedToolsText}`);
|
|
|
6418
7581
|
},
|
|
6419
7582
|
{
|
|
6420
7583
|
model: this.getModelForTask("complex"),
|
|
6421
|
-
maxTokens:
|
|
7584
|
+
maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
|
|
6422
7585
|
temperature: 0,
|
|
6423
7586
|
apiKey: this.getApiKey(apiKey),
|
|
6424
7587
|
partial: partialCallback
|
|
@@ -6454,137 +7617,14 @@ ${executedToolsText}`);
|
|
|
6454
7617
|
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
6455
7618
|
return null;
|
|
6456
7619
|
}
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
} else {
|
|
6466
|
-
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6467
|
-
if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6468
|
-
const validFieldNames = executedTool.outputSchema.fields.map((f) => f.name);
|
|
6469
|
-
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6470
|
-
const findMatchingField = (fieldName, configKey) => {
|
|
6471
|
-
if (!fieldName) return null;
|
|
6472
|
-
const lowerField = fieldName.toLowerCase();
|
|
6473
|
-
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6474
|
-
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6475
|
-
const containsMatches = validFieldNames.filter(
|
|
6476
|
-
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6477
|
-
);
|
|
6478
|
-
if (containsMatches.length === 1) return containsMatches[0];
|
|
6479
|
-
const fieldTypes = executedTool.outputSchema.fields.reduce((acc, f) => {
|
|
6480
|
-
acc[f.name] = f.type;
|
|
6481
|
-
return acc;
|
|
6482
|
-
}, {});
|
|
6483
|
-
const numericConfigKeys = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6484
|
-
const stringConfigKeys = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6485
|
-
if (numericConfigKeys.includes(configKey)) {
|
|
6486
|
-
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6487
|
-
const match = numericFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
|
|
6488
|
-
if (match) return match;
|
|
6489
|
-
if (numericFields.length > 0) {
|
|
6490
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6491
|
-
return numericFields[0];
|
|
6492
|
-
}
|
|
6493
|
-
}
|
|
6494
|
-
if (stringConfigKeys.includes(configKey)) {
|
|
6495
|
-
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6496
|
-
const match = stringFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
|
|
6497
|
-
if (match) return match;
|
|
6498
|
-
if (stringFields.length > 0) {
|
|
6499
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6500
|
-
return stringFields[0];
|
|
6501
|
-
}
|
|
6502
|
-
}
|
|
6503
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6504
|
-
return validFieldNames[0];
|
|
6505
|
-
};
|
|
6506
|
-
const configFieldsToValidate = [
|
|
6507
|
-
"xAxisKey",
|
|
6508
|
-
"yAxisKey",
|
|
6509
|
-
"valueKey",
|
|
6510
|
-
"nameKey",
|
|
6511
|
-
"labelKey",
|
|
6512
|
-
"groupBy",
|
|
6513
|
-
"aggregationField",
|
|
6514
|
-
"seriesKey",
|
|
6515
|
-
"sizeKey",
|
|
6516
|
-
"xAggregationField",
|
|
6517
|
-
"yAggregationField"
|
|
6518
|
-
];
|
|
6519
|
-
for (const configKey of configFieldsToValidate) {
|
|
6520
|
-
const fieldValue = cleanedProps.config[configKey];
|
|
6521
|
-
if (fieldValue && typeof fieldValue === "string") {
|
|
6522
|
-
if (!validFieldNames.includes(fieldValue)) {
|
|
6523
|
-
const correctedField = findMatchingField(fieldValue, configKey);
|
|
6524
|
-
if (correctedField) {
|
|
6525
|
-
logger.warn(`[${this.getProviderName()}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6526
|
-
cleanedProps.config[configKey] = correctedField;
|
|
6527
|
-
}
|
|
6528
|
-
}
|
|
6529
|
-
}
|
|
6530
|
-
}
|
|
6531
|
-
if (Array.isArray(cleanedProps.config.series)) {
|
|
6532
|
-
cleanedProps.config.series = cleanedProps.config.series.map((s) => {
|
|
6533
|
-
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6534
|
-
const correctedField = findMatchingField(s.dataKey, "yAxisKey");
|
|
6535
|
-
if (correctedField) {
|
|
6536
|
-
logger.warn(`[${this.getProviderName()}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6537
|
-
return { ...s, dataKey: correctedField };
|
|
6538
|
-
}
|
|
6539
|
-
}
|
|
6540
|
-
return s;
|
|
6541
|
-
});
|
|
6542
|
-
}
|
|
6543
|
-
}
|
|
6544
|
-
}
|
|
6545
|
-
}
|
|
6546
|
-
if (cleanedProps.query) {
|
|
6547
|
-
const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
|
|
6548
|
-
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6549
|
-
logger.warn(`[${this.getProviderName()}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6550
|
-
cleanedProps.query = null;
|
|
6551
|
-
}
|
|
6552
|
-
}
|
|
6553
|
-
if (cleanedProps.query) {
|
|
6554
|
-
const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
|
|
6555
|
-
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6556
|
-
if (fixed) {
|
|
6557
|
-
logger.warn(`[${this.getProviderName()}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6558
|
-
if (typeof cleanedProps.query === "string") {
|
|
6559
|
-
cleanedProps.query = fixedQuery;
|
|
6560
|
-
} else if (cleanedProps.query?.sql) {
|
|
6561
|
-
cleanedProps.query.sql = fixedQuery;
|
|
6562
|
-
}
|
|
6563
|
-
}
|
|
6564
|
-
}
|
|
6565
|
-
if (cleanedProps.query) {
|
|
6566
|
-
if (typeof cleanedProps.query === "string") {
|
|
6567
|
-
cleanedProps.query = ensureQueryLimit(
|
|
6568
|
-
cleanedProps.query,
|
|
6569
|
-
this.defaultLimit,
|
|
6570
|
-
10
|
|
6571
|
-
// maxLimit - enforce maximum of 10 rows for component queries
|
|
6572
|
-
);
|
|
6573
|
-
} else if (cleanedProps.query?.sql) {
|
|
6574
|
-
cleanedProps.query = {
|
|
6575
|
-
...cleanedProps.query,
|
|
6576
|
-
sql: ensureQueryLimit(
|
|
6577
|
-
cleanedProps.query.sql,
|
|
6578
|
-
this.defaultLimit,
|
|
6579
|
-
10
|
|
6580
|
-
// maxLimit - enforce maximum of 10 rows for component queries
|
|
6581
|
-
)
|
|
6582
|
-
};
|
|
6583
|
-
}
|
|
6584
|
-
}
|
|
6585
|
-
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6586
|
-
logger.info(`[${this.getProviderName()}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
6587
|
-
}
|
|
7620
|
+
const cleanedProps = processComponentProps(
|
|
7621
|
+
mc.props,
|
|
7622
|
+
executedTools,
|
|
7623
|
+
{
|
|
7624
|
+
providerName: this.getProviderName(),
|
|
7625
|
+
defaultLimit: this.defaultLimit
|
|
7626
|
+
}
|
|
7627
|
+
);
|
|
6588
7628
|
return {
|
|
6589
7629
|
...originalComponent,
|
|
6590
7630
|
props: {
|
|
@@ -6598,7 +7638,7 @@ ${executedToolsText}`);
|
|
|
6598
7638
|
logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
|
|
6599
7639
|
logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
|
|
6600
7640
|
try {
|
|
6601
|
-
const validationResult = await this.
|
|
7641
|
+
const validationResult = await this.queryService.validateComponentQueries(
|
|
6602
7642
|
finalComponents,
|
|
6603
7643
|
collections,
|
|
6604
7644
|
apiKey,
|
|
@@ -6638,153 +7678,6 @@ ${executedToolsText}`);
|
|
|
6638
7678
|
};
|
|
6639
7679
|
}
|
|
6640
7680
|
}
|
|
6641
|
-
/**
|
|
6642
|
-
* Validate a single component's query with retry logic
|
|
6643
|
-
* @param component - The component to validate
|
|
6644
|
-
* @param collections - Collections object containing database execute function
|
|
6645
|
-
* @param apiKey - Optional API key for LLM calls
|
|
6646
|
-
* @param logCollector - Optional log collector for logging
|
|
6647
|
-
* @returns Object with validated component (or null if failed) and query result
|
|
6648
|
-
*/
|
|
6649
|
-
async validateSingleComponentQuery(component, collections, apiKey, logCollector) {
|
|
6650
|
-
const MAX_RETRIES = 3;
|
|
6651
|
-
const query = component.props?.query;
|
|
6652
|
-
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6653
|
-
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6654
|
-
let finalQueryKey = originalQueryKey;
|
|
6655
|
-
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6656
|
-
let currentQueryStr = queryStr;
|
|
6657
|
-
let validated = false;
|
|
6658
|
-
let lastError = "";
|
|
6659
|
-
let result = null;
|
|
6660
|
-
let attempts = 0;
|
|
6661
|
-
logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
|
|
6662
|
-
while (attempts < MAX_RETRIES && !validated) {
|
|
6663
|
-
attempts++;
|
|
6664
|
-
try {
|
|
6665
|
-
logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
|
|
6666
|
-
const validationResult = await this.executeQueryForValidation(currentQuery, collections);
|
|
6667
|
-
result = validationResult.result;
|
|
6668
|
-
validated = true;
|
|
6669
|
-
queryCache.set(validationResult.cacheKey, result);
|
|
6670
|
-
logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6671
|
-
logCollector?.info(`\u2713 Query validated for ${component.name}`);
|
|
6672
|
-
if (currentQueryStr !== queryStr) {
|
|
6673
|
-
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6674
|
-
component.props = {
|
|
6675
|
-
...component.props,
|
|
6676
|
-
query: fixedQuery
|
|
6677
|
-
};
|
|
6678
|
-
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6679
|
-
logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
|
|
6680
|
-
}
|
|
6681
|
-
} catch (error) {
|
|
6682
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
6683
|
-
logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
|
|
6684
|
-
logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
|
|
6685
|
-
if (attempts >= MAX_RETRIES) {
|
|
6686
|
-
logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6687
|
-
logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
|
|
6688
|
-
break;
|
|
6689
|
-
}
|
|
6690
|
-
logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
|
|
6691
|
-
logCollector?.info(`Requesting query fix for ${component.name}...`);
|
|
6692
|
-
try {
|
|
6693
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
6694
|
-
currentQueryStr,
|
|
6695
|
-
lastError,
|
|
6696
|
-
{
|
|
6697
|
-
name: component.name,
|
|
6698
|
-
type: component.type,
|
|
6699
|
-
title: component.props?.title
|
|
6700
|
-
},
|
|
6701
|
-
apiKey
|
|
6702
|
-
);
|
|
6703
|
-
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6704
|
-
logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
|
|
6705
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
|
|
6706
|
-
currentQueryStr = limitedFixedQuery;
|
|
6707
|
-
if (typeof currentQuery === "string") {
|
|
6708
|
-
currentQuery = limitedFixedQuery;
|
|
6709
|
-
} else {
|
|
6710
|
-
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6711
|
-
}
|
|
6712
|
-
} else {
|
|
6713
|
-
logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
|
|
6714
|
-
break;
|
|
6715
|
-
}
|
|
6716
|
-
} catch (fixError) {
|
|
6717
|
-
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6718
|
-
logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6719
|
-
break;
|
|
6720
|
-
}
|
|
6721
|
-
}
|
|
6722
|
-
}
|
|
6723
|
-
if (!validated) {
|
|
6724
|
-
logger.warn(`[${this.getProviderName()}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6725
|
-
logCollector?.warn(`Component ${component.name} excluded from response`);
|
|
6726
|
-
}
|
|
6727
|
-
return {
|
|
6728
|
-
component: validated ? component : null,
|
|
6729
|
-
queryKey: finalQueryKey,
|
|
6730
|
-
result,
|
|
6731
|
-
validated
|
|
6732
|
-
};
|
|
6733
|
-
}
|
|
6734
|
-
/**
|
|
6735
|
-
* Validate component queries against the database and retry with LLM fixes if they fail
|
|
6736
|
-
* Uses parallel execution for faster validation
|
|
6737
|
-
* @param components - Array of components with potential queries
|
|
6738
|
-
* @param collections - Collections object containing database execute function
|
|
6739
|
-
* @param apiKey - Optional API key for LLM calls
|
|
6740
|
-
* @param logCollector - Optional log collector for logging
|
|
6741
|
-
* @returns Object with validated components and a map of query results
|
|
6742
|
-
*/
|
|
6743
|
-
async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
|
|
6744
|
-
const queryResults = /* @__PURE__ */ new Map();
|
|
6745
|
-
const validatedComponents = [];
|
|
6746
|
-
const componentsWithoutQuery = [];
|
|
6747
|
-
const componentsWithQuery = [];
|
|
6748
|
-
for (const component of components) {
|
|
6749
|
-
if (!component.props?.query) {
|
|
6750
|
-
componentsWithoutQuery.push(component);
|
|
6751
|
-
} else {
|
|
6752
|
-
componentsWithQuery.push(component);
|
|
6753
|
-
}
|
|
6754
|
-
}
|
|
6755
|
-
validatedComponents.push(...componentsWithoutQuery);
|
|
6756
|
-
if (componentsWithQuery.length === 0) {
|
|
6757
|
-
return { components: validatedComponents, queryResults };
|
|
6758
|
-
}
|
|
6759
|
-
logger.info(`[${this.getProviderName()}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6760
|
-
logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6761
|
-
const validationPromises = componentsWithQuery.map(
|
|
6762
|
-
(component) => this.validateSingleComponentQuery(component, collections, apiKey, logCollector)
|
|
6763
|
-
);
|
|
6764
|
-
const results = await Promise.allSettled(validationPromises);
|
|
6765
|
-
for (let i = 0; i < results.length; i++) {
|
|
6766
|
-
const result = results[i];
|
|
6767
|
-
const component = componentsWithQuery[i];
|
|
6768
|
-
if (result.status === "fulfilled") {
|
|
6769
|
-
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6770
|
-
if (validated && validatedComponent) {
|
|
6771
|
-
validatedComponents.push(validatedComponent);
|
|
6772
|
-
if (queryResult) {
|
|
6773
|
-
queryResults.set(queryKey, queryResult);
|
|
6774
|
-
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6775
|
-
}
|
|
6776
|
-
}
|
|
6777
|
-
} else {
|
|
6778
|
-
logger.error(`[${this.getProviderName()}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6779
|
-
logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6780
|
-
}
|
|
6781
|
-
}
|
|
6782
|
-
logger.info(`[${this.getProviderName()}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6783
|
-
return {
|
|
6784
|
-
components: validatedComponents,
|
|
6785
|
-
queryResults
|
|
6786
|
-
};
|
|
6787
|
-
}
|
|
6788
7681
|
/**
|
|
6789
7682
|
* Classify user question into category and detect external tools needed
|
|
6790
7683
|
* Determines if question is for data analysis, requires external tools, or needs text response
|
|
@@ -6809,24 +7702,8 @@ ${executedToolsText}`);
|
|
|
6809
7702
|
SCHEMA_DOC: schemaDoc || "No database schema available",
|
|
6810
7703
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6811
7704
|
});
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
if (Array.isArray(content)) {
|
|
6815
|
-
return content.map((item) => {
|
|
6816
|
-
if (typeof item === "string") return item;
|
|
6817
|
-
if (item && typeof item.text === "string") return item.text;
|
|
6818
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
6819
|
-
return JSON.stringify(item, null, 2);
|
|
6820
|
-
}).join("\n\n---\n\n");
|
|
6821
|
-
}
|
|
6822
|
-
if (content && typeof content === "object") {
|
|
6823
|
-
if (typeof content.text === "string") return content.text;
|
|
6824
|
-
return JSON.stringify(content, null, 2);
|
|
6825
|
-
}
|
|
6826
|
-
return String(content);
|
|
6827
|
-
};
|
|
6828
|
-
logger.logLLMPrompt("classifyQuestionCategory", "system", extractTextContent(prompts.system));
|
|
6829
|
-
logger.logLLMPrompt("classifyQuestionCategory", "user", extractTextContent(prompts.user));
|
|
7705
|
+
logger.logLLMPrompt("classifyQuestionCategory", "system", extractPromptText(prompts.system));
|
|
7706
|
+
logger.logLLMPrompt("classifyQuestionCategory", "user", extractPromptText(prompts.user));
|
|
6830
7707
|
const result = await LLM.stream(
|
|
6831
7708
|
{
|
|
6832
7709
|
sys: prompts.system,
|
|
@@ -6834,7 +7711,7 @@ ${executedToolsText}`);
|
|
|
6834
7711
|
},
|
|
6835
7712
|
{
|
|
6836
7713
|
model: this.getModelForTask("simple"),
|
|
6837
|
-
maxTokens:
|
|
7714
|
+
maxTokens: MAX_TOKENS_CLASSIFICATION,
|
|
6838
7715
|
temperature: 0,
|
|
6839
7716
|
apiKey: this.getApiKey(apiKey)
|
|
6840
7717
|
},
|
|
@@ -6907,7 +7784,7 @@ ${executedToolsText}`);
|
|
|
6907
7784
|
},
|
|
6908
7785
|
{
|
|
6909
7786
|
model: this.getModelForTask("complex"),
|
|
6910
|
-
maxTokens:
|
|
7787
|
+
maxTokens: MAX_TOKENS_ADAPTATION,
|
|
6911
7788
|
temperature: 0,
|
|
6912
7789
|
apiKey: this.getApiKey(apiKey)
|
|
6913
7790
|
},
|
|
@@ -7024,7 +7901,7 @@ ${executedToolsText}`);
|
|
|
7024
7901
|
prompt: userPrompt,
|
|
7025
7902
|
collections,
|
|
7026
7903
|
userId,
|
|
7027
|
-
topK:
|
|
7904
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
7028
7905
|
});
|
|
7029
7906
|
const knowledgeBaseContext = kbResult.combinedContext;
|
|
7030
7907
|
const prompts = await promptLoader.loadPrompts("text-response", {
|
|
@@ -7036,24 +7913,8 @@ ${executedToolsText}`);
|
|
|
7036
7913
|
AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc,
|
|
7037
7914
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
7038
7915
|
});
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
if (Array.isArray(content)) {
|
|
7042
|
-
return content.map((item) => {
|
|
7043
|
-
if (typeof item === "string") return item;
|
|
7044
|
-
if (item && typeof item.text === "string") return item.text;
|
|
7045
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
7046
|
-
return JSON.stringify(item, null, 2);
|
|
7047
|
-
}).join("\n\n---\n\n");
|
|
7048
|
-
}
|
|
7049
|
-
if (content && typeof content === "object") {
|
|
7050
|
-
if (typeof content.text === "string") return content.text;
|
|
7051
|
-
return JSON.stringify(content, null, 2);
|
|
7052
|
-
}
|
|
7053
|
-
return String(content);
|
|
7054
|
-
};
|
|
7055
|
-
logger.logLLMPrompt("generateTextResponse", "system", extractText(prompts.system));
|
|
7056
|
-
logger.logLLMPrompt("generateTextResponse", "user", extractText(prompts.user));
|
|
7916
|
+
logger.logLLMPrompt("generateTextResponse", "system", extractPromptText(prompts.system));
|
|
7917
|
+
logger.logLLMPrompt("generateTextResponse", "user", extractPromptText(prompts.user));
|
|
7057
7918
|
logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
|
|
7058
7919
|
logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
|
|
7059
7920
|
logCollector?.info("Generating text response with query execution capability...");
|
|
@@ -7165,324 +8026,24 @@ ${executedToolsText}`);
|
|
|
7165
8026
|
logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
|
|
7166
8027
|
logger.info(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
|
|
7167
8028
|
}
|
|
7168
|
-
const
|
|
7169
|
-
const
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
};
|
|
7185
|
-
const
|
|
7186
|
-
fullStreamedText += chunk;
|
|
7187
|
-
streamBuffer += chunk;
|
|
7188
|
-
if (chunk.includes("\n") || chunk.length > 100) {
|
|
7189
|
-
if (flushTimer) {
|
|
7190
|
-
clearTimeout(flushTimer);
|
|
7191
|
-
flushTimer = null;
|
|
7192
|
-
}
|
|
7193
|
-
flushStreamBuffer();
|
|
7194
|
-
} else if (!flushTimer) {
|
|
7195
|
-
flushTimer = setTimeout(flushStreamBuffer, FLUSH_INTERVAL);
|
|
7196
|
-
}
|
|
7197
|
-
} : void 0;
|
|
7198
|
-
const flushStream = () => {
|
|
7199
|
-
if (flushTimer) {
|
|
7200
|
-
clearTimeout(flushTimer);
|
|
7201
|
-
flushTimer = null;
|
|
7202
|
-
}
|
|
7203
|
-
flushStreamBuffer();
|
|
7204
|
-
};
|
|
7205
|
-
const streamDelay = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7206
|
-
const withProgressHeartbeat = async (operation, progressMessage, intervalMs = 1e3) => {
|
|
7207
|
-
if (!wrappedStreamCallback) {
|
|
7208
|
-
return operation();
|
|
7209
|
-
}
|
|
7210
|
-
const startTime = Date.now();
|
|
7211
|
-
await streamDelay(30);
|
|
7212
|
-
wrappedStreamCallback(`\u23F3 ${progressMessage}`);
|
|
7213
|
-
const heartbeatInterval = setInterval(() => {
|
|
7214
|
-
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
7215
|
-
if (elapsedSeconds >= 1) {
|
|
7216
|
-
wrappedStreamCallback(` (${elapsedSeconds}s)`);
|
|
7217
|
-
}
|
|
7218
|
-
}, intervalMs);
|
|
7219
|
-
try {
|
|
7220
|
-
const result2 = await operation();
|
|
7221
|
-
return result2;
|
|
7222
|
-
} finally {
|
|
7223
|
-
clearInterval(heartbeatInterval);
|
|
7224
|
-
wrappedStreamCallback("\n\n");
|
|
7225
|
-
}
|
|
7226
|
-
};
|
|
7227
|
-
const toolHandler = async (toolName, toolInput) => {
|
|
7228
|
-
if (toolName === "execute_query") {
|
|
7229
|
-
let sql = toolInput.sql;
|
|
7230
|
-
const params = toolInput.params || {};
|
|
7231
|
-
const reasoning = toolInput.reasoning;
|
|
7232
|
-
sql = ensureQueryLimit(sql, 10, 10);
|
|
7233
|
-
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
7234
|
-
const attempts = (queryAttempts.get(queryKey) || 0) + 1;
|
|
7235
|
-
queryAttempts.set(queryKey, attempts);
|
|
7236
|
-
logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
|
|
7237
|
-
if (Object.keys(params).length > 0) {
|
|
7238
|
-
logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
|
|
7239
|
-
}
|
|
7240
|
-
if (reasoning) {
|
|
7241
|
-
logCollector?.info(`Query reasoning: ${reasoning}`);
|
|
7242
|
-
}
|
|
7243
|
-
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
7244
|
-
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
7245
|
-
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
7246
|
-
logCollector?.error(errorMsg);
|
|
7247
|
-
maxAttemptsReached = true;
|
|
7248
|
-
if (wrappedStreamCallback) {
|
|
7249
|
-
wrappedStreamCallback(`
|
|
7250
|
-
|
|
7251
|
-
\u274C ${errorMsg}
|
|
7252
|
-
|
|
7253
|
-
Please try rephrasing your question or simplifying your request.
|
|
7254
|
-
|
|
7255
|
-
`);
|
|
7256
|
-
}
|
|
7257
|
-
throw new Error(errorMsg);
|
|
7258
|
-
}
|
|
7259
|
-
try {
|
|
7260
|
-
flushStream();
|
|
7261
|
-
if (wrappedStreamCallback) {
|
|
7262
|
-
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
7263
|
-
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
7264
|
-
if (attempts === 1) {
|
|
7265
|
-
wrappedStreamCallback(`
|
|
7266
|
-
|
|
7267
|
-
\u{1F50D} **Analyzing your question...**
|
|
7268
|
-
|
|
7269
|
-
`);
|
|
7270
|
-
await streamDelay(50);
|
|
7271
|
-
if (reasoning) {
|
|
7272
|
-
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
7273
|
-
|
|
7274
|
-
`);
|
|
7275
|
-
await streamDelay(50);
|
|
7276
|
-
}
|
|
7277
|
-
wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
|
|
7278
|
-
\`\`\`sql
|
|
7279
|
-
${sql}
|
|
7280
|
-
\`\`\`${paramsDisplay}
|
|
7281
|
-
|
|
7282
|
-
`);
|
|
7283
|
-
await streamDelay(50);
|
|
7284
|
-
} else {
|
|
7285
|
-
wrappedStreamCallback(`
|
|
7286
|
-
|
|
7287
|
-
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
7288
|
-
|
|
7289
|
-
`);
|
|
7290
|
-
await streamDelay(50);
|
|
7291
|
-
if (reasoning) {
|
|
7292
|
-
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
7293
|
-
|
|
7294
|
-
`);
|
|
7295
|
-
await streamDelay(50);
|
|
7296
|
-
}
|
|
7297
|
-
wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
|
|
7298
|
-
\`\`\`sql
|
|
7299
|
-
${sql}
|
|
7300
|
-
\`\`\`${paramsDisplay}
|
|
7301
|
-
|
|
7302
|
-
`);
|
|
7303
|
-
await streamDelay(50);
|
|
7304
|
-
}
|
|
7305
|
-
}
|
|
7306
|
-
logCollector?.logQuery(
|
|
7307
|
-
`Executing SQL query (attempt ${attempts})`,
|
|
7308
|
-
{ sql, params },
|
|
7309
|
-
{ reasoning, attempt: attempts }
|
|
7310
|
-
);
|
|
7311
|
-
if (!collections || !collections["database"] || !collections["database"]["execute"]) {
|
|
7312
|
-
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
7313
|
-
}
|
|
7314
|
-
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
7315
|
-
const result2 = await withProgressHeartbeat(
|
|
7316
|
-
() => collections["database"]["execute"](queryPayload),
|
|
7317
|
-
"Executing database query",
|
|
7318
|
-
800
|
|
7319
|
-
// Send heartbeat every 800ms for responsive feedback
|
|
7320
|
-
);
|
|
7321
|
-
const data = result2?.data || result2;
|
|
7322
|
-
const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7323
|
-
logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
|
|
7324
|
-
logCollector?.info(`Query successful, returned ${rowCount} rows`);
|
|
7325
|
-
if (wrappedStreamCallback) {
|
|
7326
|
-
wrappedStreamCallback(`
|
|
7327
|
-
\u2705 **Query executed successfully!**
|
|
7328
|
-
|
|
7329
|
-
`);
|
|
7330
|
-
await streamDelay(50);
|
|
7331
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
7332
|
-
const firstRow = data[0];
|
|
7333
|
-
const columns = Object.keys(firstRow);
|
|
7334
|
-
if (data.length === 1 && columns.length === 1) {
|
|
7335
|
-
const value = firstRow[columns[0]];
|
|
7336
|
-
wrappedStreamCallback(`**Result:** ${value}
|
|
7337
|
-
|
|
7338
|
-
`);
|
|
7339
|
-
await streamDelay(50);
|
|
7340
|
-
} else if (data.length > 0) {
|
|
7341
|
-
wrappedStreamCallback(`**Retrieved ${rowCount} rows**
|
|
7342
|
-
|
|
7343
|
-
`);
|
|
7344
|
-
await streamDelay(50);
|
|
7345
|
-
wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
|
|
7346
|
-
|
|
7347
|
-
`);
|
|
7348
|
-
await streamDelay(50);
|
|
7349
|
-
}
|
|
7350
|
-
} else if (Array.isArray(data) && data.length === 0) {
|
|
7351
|
-
wrappedStreamCallback(`**No rows returned.**
|
|
7352
|
-
|
|
7353
|
-
`);
|
|
7354
|
-
await streamDelay(50);
|
|
7355
|
-
}
|
|
7356
|
-
wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
|
|
7357
|
-
|
|
7358
|
-
`);
|
|
7359
|
-
}
|
|
7360
|
-
return JSON.stringify(data, null, 2);
|
|
7361
|
-
} catch (error) {
|
|
7362
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7363
|
-
logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7364
|
-
logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7365
|
-
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7366
|
-
if (wrappedStreamCallback) {
|
|
7367
|
-
wrappedStreamCallback(`\u274C **Query execution failed:**
|
|
7368
|
-
\`\`\`
|
|
7369
|
-
${errorMsg}
|
|
7370
|
-
\`\`\`
|
|
7371
|
-
|
|
7372
|
-
`);
|
|
7373
|
-
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7374
|
-
wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
|
|
7375
|
-
|
|
7376
|
-
`);
|
|
7377
|
-
}
|
|
7378
|
-
}
|
|
7379
|
-
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7380
|
-
}
|
|
7381
|
-
} else {
|
|
7382
|
-
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7383
|
-
if (externalTool) {
|
|
7384
|
-
const attempts = (toolAttempts.get(toolName) || 0) + 1;
|
|
7385
|
-
toolAttempts.set(toolName, attempts);
|
|
7386
|
-
logger.info(`[${this.getProviderName()}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
|
|
7387
|
-
logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
|
|
7388
|
-
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7389
|
-
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7390
|
-
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
7391
|
-
logCollector?.error(errorMsg);
|
|
7392
|
-
if (wrappedStreamCallback) {
|
|
7393
|
-
wrappedStreamCallback(`
|
|
7394
|
-
|
|
7395
|
-
\u274C ${errorMsg}
|
|
7396
|
-
|
|
7397
|
-
Please try rephrasing your request or contact support.
|
|
7398
|
-
|
|
7399
|
-
`);
|
|
7400
|
-
}
|
|
7401
|
-
throw new Error(errorMsg);
|
|
7402
|
-
}
|
|
7403
|
-
try {
|
|
7404
|
-
flushStream();
|
|
7405
|
-
if (wrappedStreamCallback) {
|
|
7406
|
-
if (attempts === 1) {
|
|
7407
|
-
wrappedStreamCallback(`
|
|
7408
|
-
|
|
7409
|
-
\u{1F517} **Executing ${externalTool.name}...**
|
|
7410
|
-
|
|
7411
|
-
`);
|
|
7412
|
-
} else {
|
|
7413
|
-
wrappedStreamCallback(`
|
|
7414
|
-
|
|
7415
|
-
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7416
|
-
|
|
7417
|
-
`);
|
|
7418
|
-
}
|
|
7419
|
-
await streamDelay(50);
|
|
7420
|
-
}
|
|
7421
|
-
const result2 = await withProgressHeartbeat(
|
|
7422
|
-
() => externalTool.fn(toolInput),
|
|
7423
|
-
`Running ${externalTool.name}`,
|
|
7424
|
-
800
|
|
7425
|
-
// Send heartbeat every 800ms
|
|
7426
|
-
);
|
|
7427
|
-
logger.info(`[${this.getProviderName()}] External tool ${externalTool.name} executed successfully`);
|
|
7428
|
-
logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
|
|
7429
|
-
if (!executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7430
|
-
let resultSummary = null;
|
|
7431
|
-
if (result2) {
|
|
7432
|
-
const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2);
|
|
7433
|
-
if (resultStr.length > 1e3) {
|
|
7434
|
-
resultSummary = {
|
|
7435
|
-
_preview: resultStr.substring(0, 1e3) + "... (truncated)",
|
|
7436
|
-
_totalLength: resultStr.length,
|
|
7437
|
-
_recordCount: Array.isArray(result2) ? result2.length : result2?.data?.length || result2?.contacts?.length || result2?.salesorders?.length || "unknown"
|
|
7438
|
-
};
|
|
7439
|
-
} else {
|
|
7440
|
-
resultSummary = result2;
|
|
7441
|
-
}
|
|
7442
|
-
}
|
|
7443
|
-
executedToolsList.push({
|
|
7444
|
-
id: externalTool.id,
|
|
7445
|
-
name: externalTool.name,
|
|
7446
|
-
params: toolInput,
|
|
7447
|
-
// The actual parameters used in this execution
|
|
7448
|
-
result: resultSummary,
|
|
7449
|
-
// Store summary instead of full result to save memory
|
|
7450
|
-
outputSchema: externalTool.outputSchema
|
|
7451
|
-
// Include output schema for component config generation
|
|
7452
|
-
});
|
|
7453
|
-
logger.info(`[${this.getProviderName()}] Tracked executed tool: ${externalTool.name} with params: ${JSON.stringify(toolInput)}`);
|
|
7454
|
-
}
|
|
7455
|
-
if (wrappedStreamCallback) {
|
|
7456
|
-
wrappedStreamCallback(`\u2705 **${externalTool.name} completed successfully**
|
|
7457
|
-
|
|
7458
|
-
`);
|
|
7459
|
-
await streamDelay(50);
|
|
7460
|
-
}
|
|
7461
|
-
return JSON.stringify(result2, null, 2);
|
|
7462
|
-
} catch (error) {
|
|
7463
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7464
|
-
logger.error(`[${this.getProviderName()}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7465
|
-
logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
|
|
7466
|
-
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7467
|
-
if (wrappedStreamCallback) {
|
|
7468
|
-
wrappedStreamCallback(`\u274C **${externalTool.name} failed:**
|
|
7469
|
-
\`\`\`
|
|
7470
|
-
${errorMsg}
|
|
7471
|
-
\`\`\`
|
|
7472
|
-
|
|
7473
|
-
`);
|
|
7474
|
-
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7475
|
-
wrappedStreamCallback(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7476
|
-
|
|
7477
|
-
`);
|
|
7478
|
-
}
|
|
7479
|
-
}
|
|
7480
|
-
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7481
|
-
}
|
|
7482
|
-
}
|
|
7483
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
7484
|
-
}
|
|
7485
|
-
};
|
|
8029
|
+
const streamBuffer = new StreamBuffer(streamCallback);
|
|
8030
|
+
const toolExecutor = new ToolExecutorService({
|
|
8031
|
+
providerName: this.getProviderName(),
|
|
8032
|
+
collections,
|
|
8033
|
+
streamBuffer,
|
|
8034
|
+
logCollector
|
|
8035
|
+
});
|
|
8036
|
+
const executableExternalTools = externalTools?.map((t) => ({
|
|
8037
|
+
id: t.id,
|
|
8038
|
+
name: t.name,
|
|
8039
|
+
description: t.description,
|
|
8040
|
+
fn: t.fn,
|
|
8041
|
+
limit: t.limit,
|
|
8042
|
+
outputSchema: t.outputSchema,
|
|
8043
|
+
executionType: t.executionType,
|
|
8044
|
+
userProvidedData: t.userProvidedData
|
|
8045
|
+
})) || [];
|
|
8046
|
+
const toolHandler = toolExecutor.createToolHandler(executableExternalTools);
|
|
7486
8047
|
const result = await LLM.streamWithTools(
|
|
7487
8048
|
{
|
|
7488
8049
|
sys: prompts.system,
|
|
@@ -7492,18 +8053,16 @@ ${errorMsg}
|
|
|
7492
8053
|
toolHandler,
|
|
7493
8054
|
{
|
|
7494
8055
|
model: this.getModelForTask("complex"),
|
|
7495
|
-
maxTokens:
|
|
8056
|
+
maxTokens: MAX_TOKENS_TEXT_RESPONSE,
|
|
7496
8057
|
temperature: 0,
|
|
7497
8058
|
apiKey: this.getApiKey(apiKey),
|
|
7498
|
-
partial:
|
|
7499
|
-
// Pass the wrapped streaming callback to LLM
|
|
8059
|
+
partial: streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
|
|
7500
8060
|
},
|
|
7501
|
-
|
|
7502
|
-
// max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
|
|
8061
|
+
MAX_TOOL_CALLING_ITERATIONS
|
|
7503
8062
|
);
|
|
7504
8063
|
logger.info(`[${this.getProviderName()}] Text response stream completed`);
|
|
7505
|
-
const textResponse =
|
|
7506
|
-
if (
|
|
8064
|
+
const textResponse = streamBuffer.getFullText() || result || "I apologize, but I was unable to generate a response.";
|
|
8065
|
+
if (toolExecutor.isMaxAttemptsReached()) {
|
|
7507
8066
|
const methodDuration2 = Date.now() - methodStartTime;
|
|
7508
8067
|
logger.warn(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: max attempts reached`);
|
|
7509
8068
|
logCollector?.error("Failed to generate valid query after maximum attempts");
|
|
@@ -7526,10 +8085,10 @@ ${errorMsg}
|
|
|
7526
8085
|
textLength: textResponse.length
|
|
7527
8086
|
}
|
|
7528
8087
|
);
|
|
7529
|
-
|
|
7530
|
-
if (
|
|
7531
|
-
|
|
7532
|
-
|
|
8088
|
+
streamBuffer.flush();
|
|
8089
|
+
if (streamBuffer.hasCallback() && components && components.length > 0 && category !== "general") {
|
|
8090
|
+
streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
|
|
8091
|
+
streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
|
|
7533
8092
|
}
|
|
7534
8093
|
let matchedComponents = [];
|
|
7535
8094
|
let layoutTitle = "Dashboard";
|
|
@@ -7555,11 +8114,11 @@ ${errorMsg}
|
|
|
7555
8114
|
logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions for general question`);
|
|
7556
8115
|
} else if (components && components.length > 0) {
|
|
7557
8116
|
logger.info(`[${this.getProviderName()}] Matching components from text response...`);
|
|
7558
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback setup:
|
|
7559
|
-
const componentStreamCallback =
|
|
8117
|
+
logger.info(`[${this.getProviderName()}] componentStreamCallback setup: hasCallback=${streamBuffer.hasCallback()}, category=${category}`);
|
|
8118
|
+
const componentStreamCallback = streamBuffer.hasCallback() && category !== "data_modification" ? (component) => {
|
|
7560
8119
|
logger.info(`[${this.getProviderName()}] componentStreamCallback INVOKED for: ${component.name} (${component.type})`);
|
|
7561
8120
|
const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
|
|
7562
|
-
|
|
8121
|
+
streamBuffer.write(answerMarker);
|
|
7563
8122
|
logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
|
|
7564
8123
|
} : void 0;
|
|
7565
8124
|
logger.info(`[${this.getProviderName()}] componentStreamCallback created: ${!!componentStreamCallback}`);
|
|
@@ -7586,7 +8145,7 @@ ${errorMsg}
|
|
|
7586
8145
|
logCollector,
|
|
7587
8146
|
componentStreamCallback,
|
|
7588
8147
|
deferredTools,
|
|
7589
|
-
|
|
8148
|
+
toolExecutor.getExecutedTools(),
|
|
7590
8149
|
collections,
|
|
7591
8150
|
userId
|
|
7592
8151
|
);
|
|
@@ -7670,7 +8229,7 @@ ${errorMsg}
|
|
|
7670
8229
|
userPrompt,
|
|
7671
8230
|
collections,
|
|
7672
8231
|
userId,
|
|
7673
|
-
similarityThreshold:
|
|
8232
|
+
similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
|
|
7674
8233
|
});
|
|
7675
8234
|
if (conversationMatch) {
|
|
7676
8235
|
logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
|
|
@@ -7685,7 +8244,7 @@ ${errorMsg}
|
|
|
7685
8244
|
logger.info(`[${this.getProviderName()}] Skipping cached result - Form components contain stale defaultValues, fetching fresh data`);
|
|
7686
8245
|
logCollector?.info("Skipping cache for form - fetching current values from database...");
|
|
7687
8246
|
} else if (!component) {
|
|
7688
|
-
if (conversationMatch.similarity >=
|
|
8247
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7689
8248
|
const elapsedTime2 = Date.now() - startTime;
|
|
7690
8249
|
logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response`);
|
|
7691
8250
|
logCollector?.info(`\u2713 Exact match for general question - returning cached response`);
|
|
@@ -7707,7 +8266,7 @@ ${errorMsg}
|
|
|
7707
8266
|
logCollector?.info("Similar match found but was a general conversation - processing as new question");
|
|
7708
8267
|
}
|
|
7709
8268
|
} else {
|
|
7710
|
-
if (conversationMatch.similarity >=
|
|
8269
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7711
8270
|
const elapsedTime2 = Date.now() - startTime;
|
|
7712
8271
|
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
|
|
7713
8272
|
logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
|
|
@@ -7902,7 +8461,7 @@ ${errorMsg}
|
|
|
7902
8461
|
},
|
|
7903
8462
|
{
|
|
7904
8463
|
model: this.getModelForTask("simple"),
|
|
7905
|
-
maxTokens:
|
|
8464
|
+
maxTokens: MAX_TOKENS_NEXT_QUESTIONS,
|
|
7906
8465
|
temperature: 0,
|
|
7907
8466
|
apiKey: this.getApiKey(apiKey)
|
|
7908
8467
|
},
|