@superatomai/sdk-node 0.0.70 → 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/dist/index.d.mts +89 -42
- package/dist/index.d.ts +89 -42
- package/dist/index.js +1391 -826
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1391 -826
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1516,6 +1516,351 @@ var QueryCache = class {
|
|
|
1516
1516
|
};
|
|
1517
1517
|
var queryCache = new QueryCache();
|
|
1518
1518
|
|
|
1519
|
+
// src/userResponse/llm-result-truncator.ts
|
|
1520
|
+
var DEFAULT_MAX_ROWS = 10;
|
|
1521
|
+
var DEFAULT_MAX_CHARS_PER_FIELD = 500;
|
|
1522
|
+
function inferFieldType(value) {
|
|
1523
|
+
if (value === null || value === void 0) {
|
|
1524
|
+
return "null";
|
|
1525
|
+
}
|
|
1526
|
+
if (typeof value === "string") {
|
|
1527
|
+
if (isDateString(value)) {
|
|
1528
|
+
return "date";
|
|
1529
|
+
}
|
|
1530
|
+
return "string";
|
|
1531
|
+
}
|
|
1532
|
+
if (typeof value === "number") {
|
|
1533
|
+
return "number";
|
|
1534
|
+
}
|
|
1535
|
+
if (typeof value === "boolean") {
|
|
1536
|
+
return "boolean";
|
|
1537
|
+
}
|
|
1538
|
+
if (Array.isArray(value)) {
|
|
1539
|
+
return "array";
|
|
1540
|
+
}
|
|
1541
|
+
if (typeof value === "object") {
|
|
1542
|
+
return "object";
|
|
1543
|
+
}
|
|
1544
|
+
return "unknown";
|
|
1545
|
+
}
|
|
1546
|
+
function isDateString(value) {
|
|
1547
|
+
const datePatterns = [
|
|
1548
|
+
/^\d{4}-\d{2}-\d{2}$/,
|
|
1549
|
+
// YYYY-MM-DD
|
|
1550
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
|
|
1551
|
+
// ISO 8601
|
|
1552
|
+
/^\d{2}\/\d{2}\/\d{4}$/,
|
|
1553
|
+
// MM/DD/YYYY
|
|
1554
|
+
/^\d{4}\/\d{2}\/\d{2}$/
|
|
1555
|
+
// YYYY/MM/DD
|
|
1556
|
+
];
|
|
1557
|
+
return datePatterns.some((pattern) => pattern.test(value));
|
|
1558
|
+
}
|
|
1559
|
+
function truncateTextField(value, maxLength) {
|
|
1560
|
+
if (value.length <= maxLength) {
|
|
1561
|
+
return { text: value, wasTruncated: false };
|
|
1562
|
+
}
|
|
1563
|
+
const truncated = value.substring(0, maxLength);
|
|
1564
|
+
const remaining = value.length - maxLength;
|
|
1565
|
+
return {
|
|
1566
|
+
text: `${truncated}... (${remaining} more chars)`,
|
|
1567
|
+
wasTruncated: true
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
function truncateFieldValue(value, maxCharsPerField) {
|
|
1571
|
+
if (value === null || value === void 0) {
|
|
1572
|
+
return { value, wasTruncated: false };
|
|
1573
|
+
}
|
|
1574
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1575
|
+
return { value, wasTruncated: false };
|
|
1576
|
+
}
|
|
1577
|
+
if (typeof value === "string") {
|
|
1578
|
+
const result2 = truncateTextField(value, maxCharsPerField);
|
|
1579
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1580
|
+
}
|
|
1581
|
+
if (Array.isArray(value)) {
|
|
1582
|
+
if (value.length === 0) {
|
|
1583
|
+
return { value: [], wasTruncated: false };
|
|
1584
|
+
}
|
|
1585
|
+
const preview = value.slice(0, 3);
|
|
1586
|
+
const hasMore = value.length > 3;
|
|
1587
|
+
return {
|
|
1588
|
+
value: hasMore ? `[${preview.join(", ")}... (${value.length} items)]` : value,
|
|
1589
|
+
wasTruncated: hasMore
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
if (typeof value === "object") {
|
|
1593
|
+
const jsonStr = JSON.stringify(value);
|
|
1594
|
+
const result2 = truncateTextField(jsonStr, maxCharsPerField);
|
|
1595
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1596
|
+
}
|
|
1597
|
+
const strValue = String(value);
|
|
1598
|
+
const result = truncateTextField(strValue, maxCharsPerField);
|
|
1599
|
+
return { value: result.text, wasTruncated: result.wasTruncated };
|
|
1600
|
+
}
|
|
1601
|
+
function truncateRow(row, maxCharsPerField) {
|
|
1602
|
+
const truncatedRow = {};
|
|
1603
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1604
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1605
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1606
|
+
truncatedRow[key] = result.value;
|
|
1607
|
+
if (result.wasTruncated) {
|
|
1608
|
+
truncatedFields.add(key);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
return { row: truncatedRow, truncatedFields };
|
|
1612
|
+
}
|
|
1613
|
+
function extractMetadataFromObject(obj, dataKey, maxCharsPerField) {
|
|
1614
|
+
const metadata = {};
|
|
1615
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1616
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1617
|
+
if (key === dataKey) {
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (Array.isArray(value)) {
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1624
|
+
metadata[key] = result.value;
|
|
1625
|
+
if (result.wasTruncated) {
|
|
1626
|
+
truncatedFields.add(key);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return { metadata, truncatedFields };
|
|
1630
|
+
}
|
|
1631
|
+
function extractSchema(data, truncatedFields = /* @__PURE__ */ new Set()) {
|
|
1632
|
+
if (!data || data.length === 0) {
|
|
1633
|
+
return [];
|
|
1634
|
+
}
|
|
1635
|
+
const firstRow = data[0];
|
|
1636
|
+
const schema2 = [];
|
|
1637
|
+
for (const [name, value] of Object.entries(firstRow)) {
|
|
1638
|
+
schema2.push({
|
|
1639
|
+
name,
|
|
1640
|
+
type: inferFieldType(value),
|
|
1641
|
+
truncated: truncatedFields.has(name) ? true : void 0
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
return schema2;
|
|
1645
|
+
}
|
|
1646
|
+
function truncateDataArray(data, options = {}) {
|
|
1647
|
+
const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1648
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1649
|
+
if (!data || !Array.isArray(data)) {
|
|
1650
|
+
return {
|
|
1651
|
+
data: [],
|
|
1652
|
+
totalRecords: 0,
|
|
1653
|
+
recordsShown: 0,
|
|
1654
|
+
truncatedFields: /* @__PURE__ */ new Set()
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
const totalRecords = data.length;
|
|
1658
|
+
const rowsToProcess = data.slice(0, maxRows);
|
|
1659
|
+
const truncatedData = [];
|
|
1660
|
+
const allTruncatedFields = /* @__PURE__ */ new Set();
|
|
1661
|
+
for (const row of rowsToProcess) {
|
|
1662
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(row, maxCharsPerField);
|
|
1663
|
+
truncatedData.push(truncatedRow);
|
|
1664
|
+
for (const field of truncatedFields) {
|
|
1665
|
+
allTruncatedFields.add(field);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return {
|
|
1669
|
+
data: truncatedData,
|
|
1670
|
+
totalRecords,
|
|
1671
|
+
recordsShown: truncatedData.length,
|
|
1672
|
+
truncatedFields: allTruncatedFields
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
function buildTruncationNote(totalRecords, recordsShown, truncatedFields, maxCharsPerField, sourceName) {
|
|
1676
|
+
const parts = [];
|
|
1677
|
+
if (totalRecords > recordsShown) {
|
|
1678
|
+
const source = sourceName ? ` from ${sourceName}` : "";
|
|
1679
|
+
parts.push(`Showing ${recordsShown} of ${totalRecords} total records${source}`);
|
|
1680
|
+
}
|
|
1681
|
+
if (truncatedFields.size > 0) {
|
|
1682
|
+
const fieldList = Array.from(truncatedFields).join(", ");
|
|
1683
|
+
parts.push(`Fields truncated to ${maxCharsPerField} chars: ${fieldList}`);
|
|
1684
|
+
}
|
|
1685
|
+
return parts.length > 0 ? parts.join(". ") + "." : null;
|
|
1686
|
+
}
|
|
1687
|
+
function formatQueryResultForLLM(data, options = {}) {
|
|
1688
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1689
|
+
if (!Array.isArray(data)) {
|
|
1690
|
+
if (data !== null && data !== void 0) {
|
|
1691
|
+
return {
|
|
1692
|
+
summary: {
|
|
1693
|
+
totalRecords: 1,
|
|
1694
|
+
recordsShown: 1,
|
|
1695
|
+
schema: [{ name: "result", type: inferFieldType(data) }]
|
|
1696
|
+
},
|
|
1697
|
+
data: [{ result: data }],
|
|
1698
|
+
truncationNote: null
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
return {
|
|
1702
|
+
summary: {
|
|
1703
|
+
totalRecords: 0,
|
|
1704
|
+
recordsShown: 0,
|
|
1705
|
+
schema: []
|
|
1706
|
+
},
|
|
1707
|
+
data: [],
|
|
1708
|
+
truncationNote: null
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
const {
|
|
1712
|
+
data: truncatedData,
|
|
1713
|
+
totalRecords,
|
|
1714
|
+
recordsShown,
|
|
1715
|
+
truncatedFields
|
|
1716
|
+
} = truncateDataArray(data, options);
|
|
1717
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1718
|
+
const truncationNote = buildTruncationNote(
|
|
1719
|
+
totalRecords,
|
|
1720
|
+
recordsShown,
|
|
1721
|
+
truncatedFields,
|
|
1722
|
+
maxCharsPerField,
|
|
1723
|
+
"query"
|
|
1724
|
+
);
|
|
1725
|
+
return {
|
|
1726
|
+
summary: {
|
|
1727
|
+
totalRecords,
|
|
1728
|
+
recordsShown,
|
|
1729
|
+
schema: schema2
|
|
1730
|
+
},
|
|
1731
|
+
data: truncatedData,
|
|
1732
|
+
truncationNote
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function formatToolResultForLLM(result, options = {}) {
|
|
1736
|
+
const { toolName, toolLimit } = options;
|
|
1737
|
+
const effectiveMaxRows = toolLimit ?? options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1738
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1739
|
+
if (result === null || result === void 0) {
|
|
1740
|
+
return {
|
|
1741
|
+
toolName,
|
|
1742
|
+
summary: {
|
|
1743
|
+
totalRecords: 0,
|
|
1744
|
+
recordsShown: 0,
|
|
1745
|
+
schema: []
|
|
1746
|
+
},
|
|
1747
|
+
data: [],
|
|
1748
|
+
truncationNote: null
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
if (typeof result === "string") {
|
|
1752
|
+
const { text, wasTruncated } = truncateTextField(result, maxCharsPerField);
|
|
1753
|
+
return {
|
|
1754
|
+
toolName,
|
|
1755
|
+
summary: {
|
|
1756
|
+
totalRecords: 1,
|
|
1757
|
+
recordsShown: 1,
|
|
1758
|
+
schema: [{ name: "result", type: "string", truncated: wasTruncated || void 0 }]
|
|
1759
|
+
},
|
|
1760
|
+
data: [{ result: text }],
|
|
1761
|
+
truncationNote: wasTruncated ? `Result truncated to ${maxCharsPerField} chars.` : null
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
if (Array.isArray(result)) {
|
|
1765
|
+
const {
|
|
1766
|
+
data: truncatedData,
|
|
1767
|
+
totalRecords,
|
|
1768
|
+
recordsShown,
|
|
1769
|
+
truncatedFields
|
|
1770
|
+
} = truncateDataArray(result, {
|
|
1771
|
+
maxRows: effectiveMaxRows,
|
|
1772
|
+
maxCharsPerField
|
|
1773
|
+
});
|
|
1774
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1775
|
+
const truncationNote = buildTruncationNote(
|
|
1776
|
+
totalRecords,
|
|
1777
|
+
recordsShown,
|
|
1778
|
+
truncatedFields,
|
|
1779
|
+
maxCharsPerField,
|
|
1780
|
+
toolName
|
|
1781
|
+
);
|
|
1782
|
+
return {
|
|
1783
|
+
toolName,
|
|
1784
|
+
summary: {
|
|
1785
|
+
totalRecords,
|
|
1786
|
+
recordsShown,
|
|
1787
|
+
schema: schema2
|
|
1788
|
+
},
|
|
1789
|
+
data: truncatedData,
|
|
1790
|
+
truncationNote
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
if (typeof result === "object") {
|
|
1794
|
+
const objResult = result;
|
|
1795
|
+
const dataWrapperKeys = ["data", "results", "items", "records", "rows", "list"];
|
|
1796
|
+
for (const key of dataWrapperKeys) {
|
|
1797
|
+
if (Array.isArray(objResult[key])) {
|
|
1798
|
+
const innerData = objResult[key];
|
|
1799
|
+
const {
|
|
1800
|
+
data: truncatedData,
|
|
1801
|
+
totalRecords,
|
|
1802
|
+
recordsShown,
|
|
1803
|
+
truncatedFields: dataTruncatedFields
|
|
1804
|
+
} = truncateDataArray(innerData, {
|
|
1805
|
+
maxRows: effectiveMaxRows,
|
|
1806
|
+
maxCharsPerField
|
|
1807
|
+
});
|
|
1808
|
+
const {
|
|
1809
|
+
metadata,
|
|
1810
|
+
truncatedFields: metadataTruncatedFields
|
|
1811
|
+
} = extractMetadataFromObject(objResult, key, maxCharsPerField);
|
|
1812
|
+
const allTruncatedFields = /* @__PURE__ */ new Set([...dataTruncatedFields, ...metadataTruncatedFields]);
|
|
1813
|
+
const schema3 = extractSchema(truncatedData, dataTruncatedFields);
|
|
1814
|
+
const truncationNote2 = buildTruncationNote(
|
|
1815
|
+
totalRecords,
|
|
1816
|
+
recordsShown,
|
|
1817
|
+
allTruncatedFields,
|
|
1818
|
+
maxCharsPerField,
|
|
1819
|
+
toolName
|
|
1820
|
+
);
|
|
1821
|
+
const hasMetadata = Object.keys(metadata).length > 0;
|
|
1822
|
+
return {
|
|
1823
|
+
toolName,
|
|
1824
|
+
summary: {
|
|
1825
|
+
totalRecords,
|
|
1826
|
+
recordsShown,
|
|
1827
|
+
schema: schema3
|
|
1828
|
+
},
|
|
1829
|
+
...hasMetadata && { metadata },
|
|
1830
|
+
data: truncatedData,
|
|
1831
|
+
truncationNote: truncationNote2
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(objResult, maxCharsPerField);
|
|
1836
|
+
const schema2 = extractSchema([truncatedRow], truncatedFields);
|
|
1837
|
+
const truncationNote = truncatedFields.size > 0 ? `Fields truncated to ${maxCharsPerField} chars: ${Array.from(truncatedFields).join(", ")}.` : null;
|
|
1838
|
+
return {
|
|
1839
|
+
toolName,
|
|
1840
|
+
summary: {
|
|
1841
|
+
totalRecords: 1,
|
|
1842
|
+
recordsShown: 1,
|
|
1843
|
+
schema: schema2
|
|
1844
|
+
},
|
|
1845
|
+
data: [truncatedRow],
|
|
1846
|
+
truncationNote
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
return {
|
|
1850
|
+
toolName,
|
|
1851
|
+
summary: {
|
|
1852
|
+
totalRecords: 1,
|
|
1853
|
+
recordsShown: 1,
|
|
1854
|
+
schema: [{ name: "result", type: inferFieldType(result) }]
|
|
1855
|
+
},
|
|
1856
|
+
data: [{ result }],
|
|
1857
|
+
truncationNote: null
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function formatResultAsString(formattedResult) {
|
|
1861
|
+
return JSON.stringify(formattedResult, null, 2);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1519
1864
|
// src/handlers/data-request.ts
|
|
1520
1865
|
function getQueryCacheKey(query) {
|
|
1521
1866
|
if (typeof query === "string") {
|
|
@@ -1597,15 +1942,25 @@ async function handleDataRequest(data, collections, sendMessage) {
|
|
|
1597
1942
|
}
|
|
1598
1943
|
}
|
|
1599
1944
|
if (uiBlock) {
|
|
1945
|
+
const formattedResult = formatToolResultForLLM(result, {
|
|
1946
|
+
maxRows: 3,
|
|
1947
|
+
// Only need a few sample rows for summary
|
|
1948
|
+
maxCharsPerField: 100
|
|
1949
|
+
// Short truncation for summary
|
|
1950
|
+
});
|
|
1600
1951
|
const dataSummary = {
|
|
1601
1952
|
_dataReceived: true,
|
|
1602
1953
|
_timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1603
1954
|
_collection: collection,
|
|
1604
1955
|
_operation: op,
|
|
1605
|
-
|
|
1956
|
+
_totalRecords: formattedResult.summary.totalRecords,
|
|
1957
|
+
_recordsShown: formattedResult.summary.recordsShown,
|
|
1958
|
+
_metadata: formattedResult.metadata,
|
|
1959
|
+
// Preserve totalItems, totalDeadstockItems, etc.
|
|
1960
|
+
_schema: formattedResult.summary.schema
|
|
1606
1961
|
};
|
|
1607
1962
|
uiBlock.setComponentData(dataSummary);
|
|
1608
|
-
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (
|
|
1963
|
+
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (${formattedResult.summary.totalRecords} total records)`);
|
|
1609
1964
|
} else {
|
|
1610
1965
|
logger.warn(`UIBlock ${uiBlockId} not found in threads`);
|
|
1611
1966
|
}
|
|
@@ -6022,97 +6377,335 @@ var ConversationSearch = {
|
|
|
6022
6377
|
};
|
|
6023
6378
|
var conversation_search_default = ConversationSearch;
|
|
6024
6379
|
|
|
6025
|
-
// src/userResponse/
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
6030
|
-
this.defaultLimit = config?.defaultLimit || 10;
|
|
6031
|
-
this.apiKey = config?.apiKey;
|
|
6032
|
-
this.modelStrategy = config?.modelStrategy || "fast";
|
|
6033
|
-
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
|
|
6380
|
+
// src/userResponse/prompt-extractor.ts
|
|
6381
|
+
function extractPromptText(content) {
|
|
6382
|
+
if (content === null || content === void 0) {
|
|
6383
|
+
return "";
|
|
6034
6384
|
}
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6385
|
+
if (typeof content === "string") {
|
|
6386
|
+
return content;
|
|
6387
|
+
}
|
|
6388
|
+
if (Array.isArray(content)) {
|
|
6389
|
+
return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
|
|
6390
|
+
}
|
|
6391
|
+
if (content && typeof content === "object") {
|
|
6392
|
+
return extractObjectText(content);
|
|
6393
|
+
}
|
|
6394
|
+
return String(content);
|
|
6395
|
+
}
|
|
6396
|
+
function extractContentBlockText(item) {
|
|
6397
|
+
if (typeof item === "string") {
|
|
6398
|
+
return item;
|
|
6399
|
+
}
|
|
6400
|
+
if (item && typeof item === "object") {
|
|
6401
|
+
const obj = item;
|
|
6402
|
+
if (typeof obj.text === "string") {
|
|
6403
|
+
return obj.text;
|
|
6049
6404
|
}
|
|
6405
|
+
if (typeof obj.content === "string") {
|
|
6406
|
+
return obj.content;
|
|
6407
|
+
}
|
|
6408
|
+
return JSON.stringify(item, null, 2);
|
|
6409
|
+
}
|
|
6410
|
+
return String(item);
|
|
6411
|
+
}
|
|
6412
|
+
function extractObjectText(obj) {
|
|
6413
|
+
if (typeof obj.text === "string") {
|
|
6414
|
+
return obj.text;
|
|
6415
|
+
}
|
|
6416
|
+
if (typeof obj.content === "string") {
|
|
6417
|
+
return obj.content;
|
|
6418
|
+
}
|
|
6419
|
+
return JSON.stringify(obj, null, 2);
|
|
6420
|
+
}
|
|
6421
|
+
|
|
6422
|
+
// src/userResponse/constants.ts
|
|
6423
|
+
var MAX_QUERY_VALIDATION_RETRIES = 3;
|
|
6424
|
+
var MAX_QUERY_ATTEMPTS = 6;
|
|
6425
|
+
var MAX_TOOL_ATTEMPTS = 3;
|
|
6426
|
+
var STREAM_FLUSH_INTERVAL_MS = 50;
|
|
6427
|
+
var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
|
|
6428
|
+
var STREAM_DELAY_MS = 50;
|
|
6429
|
+
var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
|
|
6430
|
+
var MAX_TOKENS_QUERY_FIX = 2048;
|
|
6431
|
+
var MAX_TOKENS_COMPONENT_MATCHING = 8192;
|
|
6432
|
+
var MAX_TOKENS_CLASSIFICATION = 1500;
|
|
6433
|
+
var MAX_TOKENS_ADAPTATION = 8192;
|
|
6434
|
+
var MAX_TOKENS_TEXT_RESPONSE = 4e3;
|
|
6435
|
+
var MAX_TOKENS_NEXT_QUESTIONS = 1200;
|
|
6436
|
+
var DEFAULT_MAX_ROWS_FOR_LLM = 10;
|
|
6437
|
+
var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
|
|
6438
|
+
var STREAM_PREVIEW_MAX_ROWS = 10;
|
|
6439
|
+
var STREAM_PREVIEW_MAX_CHARS = 200;
|
|
6440
|
+
var TOOL_TRACKING_MAX_ROWS = 5;
|
|
6441
|
+
var TOOL_TRACKING_MAX_CHARS = 200;
|
|
6442
|
+
var TOOL_TRACKING_SAMPLE_ROWS = 3;
|
|
6443
|
+
var MAX_COMPONENT_QUERY_LIMIT = 10;
|
|
6444
|
+
var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
|
|
6445
|
+
var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
|
|
6446
|
+
var MAX_TOOL_CALLING_ITERATIONS = 20;
|
|
6447
|
+
var KNOWLEDGE_BASE_TOP_K = 3;
|
|
6448
|
+
|
|
6449
|
+
// src/userResponse/stream-buffer.ts
|
|
6450
|
+
var StreamBuffer = class {
|
|
6451
|
+
constructor(callback) {
|
|
6452
|
+
this.buffer = "";
|
|
6453
|
+
this.flushTimer = null;
|
|
6454
|
+
this.fullText = "";
|
|
6455
|
+
this.callback = callback;
|
|
6050
6456
|
}
|
|
6051
6457
|
/**
|
|
6052
|
-
*
|
|
6053
|
-
* @param strategy - 'best', 'fast', or 'balanced'
|
|
6458
|
+
* Check if the buffer has a callback configured
|
|
6054
6459
|
*/
|
|
6055
|
-
|
|
6056
|
-
this.
|
|
6057
|
-
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
6460
|
+
hasCallback() {
|
|
6461
|
+
return !!this.callback;
|
|
6058
6462
|
}
|
|
6059
6463
|
/**
|
|
6060
|
-
* Get
|
|
6061
|
-
* @returns The current model strategy
|
|
6464
|
+
* Get all text that has been written (including already flushed)
|
|
6062
6465
|
*/
|
|
6063
|
-
|
|
6064
|
-
return this.
|
|
6466
|
+
getFullText() {
|
|
6467
|
+
return this.fullText;
|
|
6065
6468
|
}
|
|
6066
6469
|
/**
|
|
6067
|
-
*
|
|
6068
|
-
*
|
|
6470
|
+
* Write a chunk to the buffer
|
|
6471
|
+
* Large chunks or chunks with newlines are flushed immediately
|
|
6472
|
+
* Small chunks are batched and flushed after a short interval
|
|
6473
|
+
*
|
|
6474
|
+
* @param chunk - Text chunk to write
|
|
6069
6475
|
*/
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
this.conversationSimilarityThreshold = 0.8;
|
|
6476
|
+
write(chunk) {
|
|
6477
|
+
this.fullText += chunk;
|
|
6478
|
+
if (!this.callback) {
|
|
6074
6479
|
return;
|
|
6075
6480
|
}
|
|
6076
|
-
this.
|
|
6077
|
-
|
|
6481
|
+
this.buffer += chunk;
|
|
6482
|
+
if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
|
|
6483
|
+
this.flushNow();
|
|
6484
|
+
} else if (!this.flushTimer) {
|
|
6485
|
+
this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
|
|
6486
|
+
}
|
|
6078
6487
|
}
|
|
6079
6488
|
/**
|
|
6080
|
-
*
|
|
6081
|
-
*
|
|
6489
|
+
* Flush the buffer immediately
|
|
6490
|
+
* Call this before tool execution or other operations that need clean output
|
|
6082
6491
|
*/
|
|
6083
|
-
|
|
6084
|
-
|
|
6492
|
+
flush() {
|
|
6493
|
+
this.flushNow();
|
|
6085
6494
|
}
|
|
6086
6495
|
/**
|
|
6087
|
-
*
|
|
6496
|
+
* Internal flush implementation
|
|
6088
6497
|
*/
|
|
6089
|
-
|
|
6090
|
-
|
|
6498
|
+
flushNow() {
|
|
6499
|
+
if (this.flushTimer) {
|
|
6500
|
+
clearTimeout(this.flushTimer);
|
|
6501
|
+
this.flushTimer = null;
|
|
6502
|
+
}
|
|
6503
|
+
if (this.buffer && this.callback) {
|
|
6504
|
+
this.callback(this.buffer);
|
|
6505
|
+
this.buffer = "";
|
|
6506
|
+
}
|
|
6091
6507
|
}
|
|
6092
6508
|
/**
|
|
6093
|
-
*
|
|
6094
|
-
*
|
|
6095
|
-
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
6509
|
+
* Clean up resources
|
|
6510
|
+
* Call this when done with the buffer
|
|
6096
6511
|
*/
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6512
|
+
dispose() {
|
|
6513
|
+
this.flush();
|
|
6514
|
+
this.callback = void 0;
|
|
6515
|
+
}
|
|
6516
|
+
};
|
|
6517
|
+
function streamDelay(ms = STREAM_DELAY_MS) {
|
|
6518
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6519
|
+
}
|
|
6520
|
+
async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
|
|
6521
|
+
if (!streamBuffer.hasCallback()) {
|
|
6522
|
+
return operation();
|
|
6523
|
+
}
|
|
6524
|
+
const startTime = Date.now();
|
|
6525
|
+
await streamDelay(30);
|
|
6526
|
+
streamBuffer.write(`\u23F3 ${progressMessage}`);
|
|
6527
|
+
const heartbeatInterval = setInterval(() => {
|
|
6528
|
+
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
6529
|
+
if (elapsedSeconds >= 1) {
|
|
6530
|
+
streamBuffer.write(` (${elapsedSeconds}s)`);
|
|
6531
|
+
}
|
|
6532
|
+
}, intervalMs);
|
|
6533
|
+
try {
|
|
6534
|
+
const result = await operation();
|
|
6535
|
+
return result;
|
|
6536
|
+
} finally {
|
|
6537
|
+
clearInterval(heartbeatInterval);
|
|
6538
|
+
streamBuffer.write("\n\n");
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
|
|
6542
|
+
// src/userResponse/utils/component-props-processor.ts
|
|
6543
|
+
var NUMERIC_CONFIG_KEYS = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6544
|
+
var STRING_CONFIG_KEYS = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6545
|
+
var CONFIG_FIELDS_TO_VALIDATE = [
|
|
6546
|
+
"xAxisKey",
|
|
6547
|
+
"yAxisKey",
|
|
6548
|
+
"valueKey",
|
|
6549
|
+
"nameKey",
|
|
6550
|
+
"labelKey",
|
|
6551
|
+
"groupBy",
|
|
6552
|
+
"aggregationField",
|
|
6553
|
+
"seriesKey",
|
|
6554
|
+
"sizeKey",
|
|
6555
|
+
"xAggregationField",
|
|
6556
|
+
"yAggregationField"
|
|
6557
|
+
];
|
|
6558
|
+
function findMatchingField(fieldName, configKey, validFieldNames, fieldTypes, providerName) {
|
|
6559
|
+
if (!fieldName) return null;
|
|
6560
|
+
const lowerField = fieldName.toLowerCase();
|
|
6561
|
+
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6562
|
+
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6563
|
+
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6564
|
+
const containsMatches = validFieldNames.filter(
|
|
6565
|
+
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6566
|
+
);
|
|
6567
|
+
if (containsMatches.length === 1) return containsMatches[0];
|
|
6568
|
+
if (NUMERIC_CONFIG_KEYS.includes(configKey)) {
|
|
6569
|
+
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6570
|
+
const match = numericFields.find(
|
|
6571
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6572
|
+
);
|
|
6573
|
+
if (match) return match;
|
|
6574
|
+
if (numericFields.length > 0) {
|
|
6575
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6576
|
+
return numericFields[0];
|
|
6101
6577
|
}
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6578
|
+
}
|
|
6579
|
+
if (STRING_CONFIG_KEYS.includes(configKey)) {
|
|
6580
|
+
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6581
|
+
const match = stringFields.find(
|
|
6582
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6583
|
+
);
|
|
6584
|
+
if (match) return match;
|
|
6585
|
+
if (stringFields.length > 0) {
|
|
6586
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6587
|
+
return stringFields[0];
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6591
|
+
return validFieldNames[0];
|
|
6592
|
+
}
|
|
6593
|
+
function validateConfigFieldNames(config, outputSchema, providerName) {
|
|
6594
|
+
if (!outputSchema?.fields || !config) return config;
|
|
6595
|
+
const validFieldNames = outputSchema.fields.map((f) => f.name);
|
|
6596
|
+
const fieldTypes = outputSchema.fields.reduce((acc, f) => {
|
|
6597
|
+
acc[f.name] = f.type;
|
|
6598
|
+
return acc;
|
|
6599
|
+
}, {});
|
|
6600
|
+
const correctedConfig = { ...config };
|
|
6601
|
+
for (const configKey of CONFIG_FIELDS_TO_VALIDATE) {
|
|
6602
|
+
const fieldValue = correctedConfig[configKey];
|
|
6603
|
+
if (fieldValue && typeof fieldValue === "string") {
|
|
6604
|
+
if (!validFieldNames.includes(fieldValue)) {
|
|
6605
|
+
const correctedField = findMatchingField(fieldValue, configKey, validFieldNames, fieldTypes, providerName);
|
|
6606
|
+
if (correctedField) {
|
|
6607
|
+
logger.warn(`[${providerName}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6608
|
+
correctedConfig[configKey] = correctedField;
|
|
6107
6609
|
}
|
|
6108
6610
|
}
|
|
6109
6611
|
}
|
|
6110
|
-
|
|
6612
|
+
}
|
|
6613
|
+
if (Array.isArray(correctedConfig.series)) {
|
|
6614
|
+
correctedConfig.series = correctedConfig.series.map((s) => {
|
|
6615
|
+
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6616
|
+
const correctedField = findMatchingField(s.dataKey, "yAxisKey", validFieldNames, fieldTypes, providerName);
|
|
6617
|
+
if (correctedField) {
|
|
6618
|
+
logger.warn(`[${providerName}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6619
|
+
return { ...s, dataKey: correctedField };
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
return s;
|
|
6623
|
+
});
|
|
6624
|
+
}
|
|
6625
|
+
return correctedConfig;
|
|
6626
|
+
}
|
|
6627
|
+
function validateExternalTool(externalTool, executedTools, providerName) {
|
|
6628
|
+
if (!externalTool) {
|
|
6629
|
+
return { valid: true };
|
|
6630
|
+
}
|
|
6631
|
+
const toolId = externalTool.toolId;
|
|
6632
|
+
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
6633
|
+
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
6634
|
+
if (!isValidTool) {
|
|
6635
|
+
logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
6636
|
+
return { valid: false };
|
|
6637
|
+
}
|
|
6638
|
+
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6639
|
+
return { valid: true, executedTool };
|
|
6640
|
+
}
|
|
6641
|
+
function validateAndCleanQuery(query, config) {
|
|
6642
|
+
if (!query) {
|
|
6643
|
+
return { query: null, wasModified: false };
|
|
6644
|
+
}
|
|
6645
|
+
let wasModified = false;
|
|
6646
|
+
let cleanedQuery = query;
|
|
6647
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6648
|
+
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6649
|
+
logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6650
|
+
return { query: null, wasModified: true };
|
|
6651
|
+
}
|
|
6652
|
+
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6653
|
+
if (fixed) {
|
|
6654
|
+
logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6655
|
+
wasModified = true;
|
|
6656
|
+
if (typeof cleanedQuery === "string") {
|
|
6657
|
+
cleanedQuery = fixedQuery;
|
|
6658
|
+
} else if (cleanedQuery?.sql) {
|
|
6659
|
+
cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
if (typeof cleanedQuery === "string") {
|
|
6663
|
+
const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6664
|
+
if (limitedQuery !== cleanedQuery) wasModified = true;
|
|
6665
|
+
cleanedQuery = limitedQuery;
|
|
6666
|
+
} else if (cleanedQuery?.sql) {
|
|
6667
|
+
const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6668
|
+
if (limitedSql !== cleanedQuery.sql) wasModified = true;
|
|
6669
|
+
cleanedQuery = { ...cleanedQuery, sql: limitedSql };
|
|
6670
|
+
}
|
|
6671
|
+
return { query: cleanedQuery, wasModified };
|
|
6672
|
+
}
|
|
6673
|
+
function processComponentProps(props, executedTools, config) {
|
|
6674
|
+
let cleanedProps = { ...props };
|
|
6675
|
+
if (cleanedProps.externalTool) {
|
|
6676
|
+
const { valid, executedTool } = validateExternalTool(
|
|
6677
|
+
cleanedProps.externalTool,
|
|
6678
|
+
executedTools,
|
|
6679
|
+
config.providerName
|
|
6680
|
+
);
|
|
6681
|
+
if (!valid) {
|
|
6682
|
+
cleanedProps.externalTool = null;
|
|
6683
|
+
} else if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6684
|
+
cleanedProps.config = validateConfigFieldNames(
|
|
6685
|
+
cleanedProps.config,
|
|
6686
|
+
executedTool.outputSchema,
|
|
6687
|
+
config.providerName
|
|
6688
|
+
);
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6691
|
+
if (cleanedProps.query) {
|
|
6692
|
+
const { query } = validateAndCleanQuery(cleanedProps.query, config);
|
|
6693
|
+
cleanedProps.query = query;
|
|
6694
|
+
}
|
|
6695
|
+
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6696
|
+
logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
6697
|
+
}
|
|
6698
|
+
return cleanedProps;
|
|
6699
|
+
}
|
|
6700
|
+
|
|
6701
|
+
// src/userResponse/services/query-execution-service.ts
|
|
6702
|
+
var QueryExecutionService = class {
|
|
6703
|
+
constructor(config) {
|
|
6704
|
+
this.config = config;
|
|
6111
6705
|
}
|
|
6112
6706
|
/**
|
|
6113
|
-
* Get the cache key for a query
|
|
6707
|
+
* Get the cache key for a query
|
|
6114
6708
|
* This ensures the cache key matches what the frontend will send
|
|
6115
|
-
* Used for both caching and internal deduplication
|
|
6116
6709
|
*/
|
|
6117
6710
|
getQueryCacheKey(query) {
|
|
6118
6711
|
if (typeof query === "string") {
|
|
@@ -6128,17 +6721,19 @@ var BaseLLM = class {
|
|
|
6128
6721
|
return "";
|
|
6129
6722
|
}
|
|
6130
6723
|
/**
|
|
6131
|
-
* Execute a query against the database
|
|
6724
|
+
* Execute a query against the database
|
|
6132
6725
|
* @param query - The SQL query to execute (string or object with sql/values)
|
|
6133
6726
|
* @param collections - Collections object containing database execute function
|
|
6134
6727
|
* @returns Object with result data and cache key
|
|
6135
|
-
* @throws Error if query execution fails
|
|
6136
6728
|
*/
|
|
6137
|
-
async
|
|
6729
|
+
async executeQuery(query, collections) {
|
|
6138
6730
|
const cacheKey = this.getQueryCacheKey(query);
|
|
6139
6731
|
if (!cacheKey) {
|
|
6140
6732
|
throw new Error("Invalid query format: expected string or object with sql property");
|
|
6141
6733
|
}
|
|
6734
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
6735
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
6736
|
+
}
|
|
6142
6737
|
const result = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6143
6738
|
return { result, cacheKey };
|
|
6144
6739
|
}
|
|
@@ -6146,7 +6741,7 @@ var BaseLLM = class {
|
|
|
6146
6741
|
* Request the LLM to fix a failed SQL query
|
|
6147
6742
|
* @param failedQuery - The query that failed execution
|
|
6148
6743
|
* @param errorMessage - The error message from the failed execution
|
|
6149
|
-
* @param componentContext - Context about the component
|
|
6744
|
+
* @param componentContext - Context about the component
|
|
6150
6745
|
* @param apiKey - Optional API key
|
|
6151
6746
|
* @returns Fixed query string
|
|
6152
6747
|
*/
|
|
@@ -6187,10 +6782,10 @@ Fixed SQL query:`;
|
|
|
6187
6782
|
user: prompt
|
|
6188
6783
|
},
|
|
6189
6784
|
{
|
|
6190
|
-
model: this.getModelForTask("simple"),
|
|
6191
|
-
maxTokens:
|
|
6785
|
+
model: this.config.getModelForTask("simple"),
|
|
6786
|
+
maxTokens: MAX_TOKENS_QUERY_FIX,
|
|
6192
6787
|
temperature: 0,
|
|
6193
|
-
apiKey: this.getApiKey(apiKey)
|
|
6788
|
+
apiKey: this.config.getApiKey(apiKey)
|
|
6194
6789
|
}
|
|
6195
6790
|
);
|
|
6196
6791
|
let fixedQuery = response.trim();
|
|
@@ -6200,81 +6795,656 @@ Fixed SQL query:`;
|
|
|
6200
6795
|
return validatedQuery;
|
|
6201
6796
|
}
|
|
6202
6797
|
/**
|
|
6203
|
-
*
|
|
6204
|
-
*
|
|
6205
|
-
*
|
|
6206
|
-
*
|
|
6207
|
-
* @param
|
|
6208
|
-
* @
|
|
6209
|
-
* @param apiKey - Optional API key
|
|
6210
|
-
* @param logCollector - Optional log collector
|
|
6211
|
-
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
6212
|
-
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
6798
|
+
* Validate a single component's query with retry logic
|
|
6799
|
+
* @param component - The component to validate
|
|
6800
|
+
* @param collections - Collections object containing database execute function
|
|
6801
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6802
|
+
* @param logCollector - Optional log collector for logging
|
|
6803
|
+
* @returns Validation result with component, query key, and result
|
|
6213
6804
|
*/
|
|
6214
|
-
async
|
|
6215
|
-
const
|
|
6216
|
-
const
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
logger.
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6805
|
+
async validateSingleQuery(component, collections, apiKey, logCollector) {
|
|
6806
|
+
const query = component.props?.query;
|
|
6807
|
+
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6808
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6809
|
+
let finalQueryKey = originalQueryKey;
|
|
6810
|
+
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6811
|
+
let currentQueryStr = queryStr;
|
|
6812
|
+
let validated = false;
|
|
6813
|
+
let lastError = "";
|
|
6814
|
+
let result = null;
|
|
6815
|
+
let attempts = 0;
|
|
6816
|
+
logger.info(`[${this.config.providerName}] Validating query for component: ${component.name} (${component.type})`);
|
|
6817
|
+
while (attempts < MAX_QUERY_VALIDATION_RETRIES && !validated) {
|
|
6818
|
+
attempts++;
|
|
6819
|
+
try {
|
|
6820
|
+
logger.debug(`[${this.config.providerName}] Query validation attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES} for ${component.name}`);
|
|
6821
|
+
const validationResult = await this.executeQuery(currentQuery, collections);
|
|
6822
|
+
result = validationResult.result;
|
|
6823
|
+
validated = true;
|
|
6824
|
+
queryCache.set(validationResult.cacheKey, result);
|
|
6825
|
+
logger.info(`[${this.config.providerName}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6826
|
+
logCollector?.info(`\u2713 Query validated for ${component.name}`);
|
|
6827
|
+
if (currentQueryStr !== queryStr) {
|
|
6828
|
+
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6829
|
+
component.props = {
|
|
6830
|
+
...component.props,
|
|
6831
|
+
query: fixedQuery
|
|
6832
|
+
};
|
|
6833
|
+
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6834
|
+
logger.info(`[${this.config.providerName}] Updated ${component.name} with fixed query`);
|
|
6835
|
+
}
|
|
6836
|
+
} catch (error) {
|
|
6837
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
6838
|
+
logger.warn(`[${this.config.providerName}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES}): ${lastError}`);
|
|
6839
|
+
logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
|
|
6840
|
+
if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
|
|
6841
|
+
logger.error(`[${this.config.providerName}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6842
|
+
logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
|
|
6843
|
+
break;
|
|
6844
|
+
}
|
|
6845
|
+
logger.info(`[${this.config.providerName}] Requesting query fix from LLM for ${component.name}...`);
|
|
6846
|
+
logCollector?.info(`Requesting query fix for ${component.name}...`);
|
|
6847
|
+
try {
|
|
6848
|
+
const fixedQueryStr = await this.requestQueryFix(
|
|
6849
|
+
currentQueryStr,
|
|
6850
|
+
lastError,
|
|
6851
|
+
{
|
|
6852
|
+
name: component.name,
|
|
6853
|
+
type: component.type,
|
|
6854
|
+
title: component.props?.title
|
|
6855
|
+
},
|
|
6856
|
+
apiKey
|
|
6857
|
+
);
|
|
6858
|
+
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6859
|
+
logger.info(`[${this.config.providerName}] Received fixed query for ${component.name}, retrying...`);
|
|
6860
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6861
|
+
currentQueryStr = limitedFixedQuery;
|
|
6862
|
+
if (typeof currentQuery === "string") {
|
|
6863
|
+
currentQuery = limitedFixedQuery;
|
|
6864
|
+
} else {
|
|
6865
|
+
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6866
|
+
}
|
|
6867
|
+
} else {
|
|
6868
|
+
logger.warn(`[${this.config.providerName}] LLM returned same or empty query, stopping retries`);
|
|
6869
|
+
break;
|
|
6870
|
+
}
|
|
6871
|
+
} catch (fixError) {
|
|
6872
|
+
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6873
|
+
logger.error(`[${this.config.providerName}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6874
|
+
break;
|
|
6875
|
+
}
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
if (!validated) {
|
|
6879
|
+
logger.warn(`[${this.config.providerName}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6880
|
+
logCollector?.warn(`Component ${component.name} excluded from response`);
|
|
6881
|
+
}
|
|
6882
|
+
return {
|
|
6883
|
+
component: validated ? component : null,
|
|
6884
|
+
queryKey: finalQueryKey,
|
|
6885
|
+
result,
|
|
6886
|
+
validated
|
|
6887
|
+
};
|
|
6888
|
+
}
|
|
6889
|
+
/**
|
|
6890
|
+
* Validate multiple component queries in parallel
|
|
6891
|
+
* @param components - Array of components with potential queries
|
|
6892
|
+
* @param collections - Collections object containing database execute function
|
|
6893
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6894
|
+
* @param logCollector - Optional log collector for logging
|
|
6895
|
+
* @returns Object with validated components and query results map
|
|
6896
|
+
*/
|
|
6897
|
+
async validateComponentQueries(components, collections, apiKey, logCollector) {
|
|
6898
|
+
const queryResults = /* @__PURE__ */ new Map();
|
|
6899
|
+
const validatedComponents = [];
|
|
6900
|
+
const componentsWithoutQuery = [];
|
|
6901
|
+
const componentsWithQuery = [];
|
|
6902
|
+
for (const component of components) {
|
|
6903
|
+
if (!component.props?.query) {
|
|
6904
|
+
componentsWithoutQuery.push(component);
|
|
6905
|
+
} else {
|
|
6906
|
+
componentsWithQuery.push(component);
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6909
|
+
validatedComponents.push(...componentsWithoutQuery);
|
|
6910
|
+
if (componentsWithQuery.length === 0) {
|
|
6911
|
+
return { components: validatedComponents, queryResults };
|
|
6912
|
+
}
|
|
6913
|
+
logger.info(`[${this.config.providerName}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6914
|
+
logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6915
|
+
const validationPromises = componentsWithQuery.map(
|
|
6916
|
+
(component) => this.validateSingleQuery(component, collections, apiKey, logCollector)
|
|
6917
|
+
);
|
|
6918
|
+
const results = await Promise.allSettled(validationPromises);
|
|
6919
|
+
for (let i = 0; i < results.length; i++) {
|
|
6920
|
+
const result = results[i];
|
|
6921
|
+
const component = componentsWithQuery[i];
|
|
6922
|
+
if (result.status === "fulfilled") {
|
|
6923
|
+
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6924
|
+
if (validated && validatedComponent) {
|
|
6925
|
+
validatedComponents.push(validatedComponent);
|
|
6926
|
+
if (queryResult) {
|
|
6927
|
+
queryResults.set(queryKey, queryResult);
|
|
6928
|
+
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6931
|
+
} else {
|
|
6932
|
+
logger.error(`[${this.config.providerName}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6933
|
+
logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
logger.info(`[${this.config.providerName}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6937
|
+
return {
|
|
6938
|
+
components: validatedComponents,
|
|
6939
|
+
queryResults
|
|
6940
|
+
};
|
|
6941
|
+
}
|
|
6942
|
+
};
|
|
6943
|
+
|
|
6944
|
+
// src/userResponse/services/tool-executor-service.ts
|
|
6945
|
+
var ToolExecutorService = class {
|
|
6946
|
+
constructor(config) {
|
|
6947
|
+
this.queryAttempts = /* @__PURE__ */ new Map();
|
|
6948
|
+
this.toolAttempts = /* @__PURE__ */ new Map();
|
|
6949
|
+
this.executedToolsList = [];
|
|
6950
|
+
this.maxAttemptsReached = false;
|
|
6951
|
+
this.config = config;
|
|
6952
|
+
}
|
|
6953
|
+
/**
|
|
6954
|
+
* Reset state for a new execution
|
|
6955
|
+
*/
|
|
6956
|
+
reset() {
|
|
6957
|
+
this.queryAttempts.clear();
|
|
6958
|
+
this.toolAttempts.clear();
|
|
6959
|
+
this.executedToolsList = [];
|
|
6960
|
+
this.maxAttemptsReached = false;
|
|
6961
|
+
}
|
|
6962
|
+
/**
|
|
6963
|
+
* Get list of successfully executed tools
|
|
6964
|
+
*/
|
|
6965
|
+
getExecutedTools() {
|
|
6966
|
+
return this.executedToolsList;
|
|
6967
|
+
}
|
|
6968
|
+
/**
|
|
6969
|
+
* Check if max attempts were reached
|
|
6970
|
+
*/
|
|
6971
|
+
isMaxAttemptsReached() {
|
|
6972
|
+
return this.maxAttemptsReached;
|
|
6973
|
+
}
|
|
6974
|
+
/**
|
|
6975
|
+
* Create a tool handler function for LLM.streamWithTools
|
|
6976
|
+
* @param externalTools - List of available external tools
|
|
6977
|
+
* @returns Tool handler function
|
|
6978
|
+
*/
|
|
6979
|
+
createToolHandler(externalTools) {
|
|
6980
|
+
return async (toolName, toolInput) => {
|
|
6981
|
+
if (toolName === "execute_query") {
|
|
6982
|
+
return this.executeQuery(toolInput);
|
|
6983
|
+
} else {
|
|
6984
|
+
return this.executeExternalTool(toolName, toolInput, externalTools);
|
|
6985
|
+
}
|
|
6986
|
+
};
|
|
6987
|
+
}
|
|
6988
|
+
/**
|
|
6989
|
+
* Execute a SQL query with retry tracking and streaming feedback
|
|
6990
|
+
*/
|
|
6991
|
+
async executeQuery(toolInput) {
|
|
6992
|
+
let sql = toolInput.sql;
|
|
6993
|
+
const params = toolInput.params || {};
|
|
6994
|
+
const reasoning = toolInput.reasoning;
|
|
6995
|
+
const { streamBuffer, collections, logCollector, providerName } = this.config;
|
|
6996
|
+
sql = ensureQueryLimit(sql, MAX_COMPONENT_QUERY_LIMIT, MAX_COMPONENT_QUERY_LIMIT);
|
|
6997
|
+
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
6998
|
+
const attempts = (this.queryAttempts.get(queryKey) || 0) + 1;
|
|
6999
|
+
this.queryAttempts.set(queryKey, attempts);
|
|
7000
|
+
logger.info(`[${providerName}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
|
|
7001
|
+
if (Object.keys(params).length > 0) {
|
|
7002
|
+
logger.info(`[${providerName}] Query params: ${JSON.stringify(params)}`);
|
|
7003
|
+
}
|
|
7004
|
+
if (reasoning) {
|
|
7005
|
+
logCollector?.info(`Query reasoning: ${reasoning}`);
|
|
7006
|
+
}
|
|
7007
|
+
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
7008
|
+
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
7009
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
7010
|
+
logCollector?.error(errorMsg);
|
|
7011
|
+
this.maxAttemptsReached = true;
|
|
7012
|
+
if (streamBuffer.hasCallback()) {
|
|
7013
|
+
streamBuffer.write(`
|
|
7014
|
+
|
|
7015
|
+
\u274C ${errorMsg}
|
|
7016
|
+
|
|
7017
|
+
Please try rephrasing your question or simplifying your request.
|
|
7018
|
+
|
|
7019
|
+
`);
|
|
7020
|
+
}
|
|
7021
|
+
throw new Error(errorMsg);
|
|
7022
|
+
}
|
|
7023
|
+
try {
|
|
7024
|
+
streamBuffer.flush();
|
|
7025
|
+
if (streamBuffer.hasCallback()) {
|
|
7026
|
+
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
7027
|
+
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
7028
|
+
if (attempts === 1) {
|
|
7029
|
+
streamBuffer.write(`
|
|
7030
|
+
|
|
7031
|
+
\u{1F50D} **Analyzing your question...**
|
|
7032
|
+
|
|
7033
|
+
`);
|
|
7034
|
+
await streamDelay();
|
|
7035
|
+
if (reasoning) {
|
|
7036
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
7037
|
+
|
|
7038
|
+
`);
|
|
7039
|
+
await streamDelay();
|
|
7040
|
+
}
|
|
7041
|
+
streamBuffer.write(`\u{1F4DD} **Generated SQL Query:**
|
|
7042
|
+
\`\`\`sql
|
|
7043
|
+
${sql}
|
|
7044
|
+
\`\`\`${paramsDisplay}
|
|
7045
|
+
|
|
7046
|
+
`);
|
|
7047
|
+
await streamDelay();
|
|
7048
|
+
} else {
|
|
7049
|
+
streamBuffer.write(`
|
|
7050
|
+
|
|
7051
|
+
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
7052
|
+
|
|
7053
|
+
`);
|
|
7054
|
+
await streamDelay();
|
|
7055
|
+
if (reasoning) {
|
|
7056
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
7057
|
+
|
|
7058
|
+
`);
|
|
7059
|
+
await streamDelay();
|
|
7060
|
+
}
|
|
7061
|
+
streamBuffer.write(`\u{1F4DD} **Corrected SQL Query:**
|
|
7062
|
+
\`\`\`sql
|
|
7063
|
+
${sql}
|
|
7064
|
+
\`\`\`${paramsDisplay}
|
|
7065
|
+
|
|
7066
|
+
`);
|
|
7067
|
+
await streamDelay();
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
logCollector?.logQuery?.(
|
|
7071
|
+
`Executing SQL query (attempt ${attempts})`,
|
|
7072
|
+
{ sql, params },
|
|
7073
|
+
{ reasoning, attempt: attempts }
|
|
7074
|
+
);
|
|
7075
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
7076
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
7077
|
+
}
|
|
7078
|
+
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
7079
|
+
const result = await withProgressHeartbeat(
|
|
7080
|
+
() => collections["database"]["execute"](queryPayload),
|
|
7081
|
+
"Executing database query",
|
|
7082
|
+
streamBuffer
|
|
7083
|
+
);
|
|
7084
|
+
const data = result?.data || result;
|
|
7085
|
+
const rowCount = result?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7086
|
+
logger.info(`[${providerName}] Query executed successfully, rows returned: ${rowCount}`);
|
|
7087
|
+
logCollector?.info(`Query successful, returned ${rowCount} rows`);
|
|
7088
|
+
if (streamBuffer.hasCallback()) {
|
|
7089
|
+
streamBuffer.write(`
|
|
7090
|
+
\u2705 **Query executed successfully!**
|
|
7091
|
+
|
|
7092
|
+
`);
|
|
7093
|
+
await streamDelay();
|
|
7094
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
7095
|
+
const firstRow = data[0];
|
|
7096
|
+
const columns = Object.keys(firstRow);
|
|
7097
|
+
if (data.length === 1 && columns.length === 1) {
|
|
7098
|
+
const value = firstRow[columns[0]];
|
|
7099
|
+
streamBuffer.write(`**Result:** ${value}
|
|
7100
|
+
|
|
7101
|
+
`);
|
|
7102
|
+
await streamDelay();
|
|
7103
|
+
} else if (data.length > 0) {
|
|
7104
|
+
streamBuffer.write(`**Retrieved ${rowCount} rows**
|
|
7105
|
+
|
|
7106
|
+
`);
|
|
7107
|
+
await streamDelay();
|
|
7108
|
+
const streamPreview = formatQueryResultForLLM(data, {
|
|
7109
|
+
maxRows: STREAM_PREVIEW_MAX_ROWS,
|
|
7110
|
+
maxCharsPerField: STREAM_PREVIEW_MAX_CHARS
|
|
7111
|
+
});
|
|
7112
|
+
streamBuffer.write(`<DataTable>${JSON.stringify(streamPreview.data)}</DataTable>
|
|
7113
|
+
|
|
7114
|
+
`);
|
|
7115
|
+
if (streamPreview.truncationNote) {
|
|
7116
|
+
streamBuffer.write(`*${streamPreview.truncationNote}*
|
|
7117
|
+
|
|
7118
|
+
`);
|
|
7119
|
+
}
|
|
7120
|
+
await streamDelay();
|
|
7121
|
+
}
|
|
7122
|
+
} else if (Array.isArray(data) && data.length === 0) {
|
|
7123
|
+
streamBuffer.write(`**No rows returned.**
|
|
7124
|
+
|
|
7125
|
+
`);
|
|
7126
|
+
await streamDelay();
|
|
7127
|
+
}
|
|
7128
|
+
streamBuffer.write(`\u{1F4CA} **Analyzing results...**
|
|
7129
|
+
|
|
7130
|
+
`);
|
|
7131
|
+
}
|
|
7132
|
+
const formattedResult = formatQueryResultForLLM(data, {
|
|
7133
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7134
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7135
|
+
});
|
|
7136
|
+
logger.info(`[${providerName}] Query result formatted: ${formattedResult.summary.recordsShown}/${formattedResult.summary.totalRecords} records`);
|
|
7137
|
+
if (formattedResult.truncationNote) {
|
|
7138
|
+
logger.info(`[${providerName}] Truncation: ${formattedResult.truncationNote}`);
|
|
7139
|
+
}
|
|
7140
|
+
return formatResultAsString(formattedResult);
|
|
7141
|
+
} catch (error) {
|
|
7142
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7143
|
+
logger.error(`[${providerName}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7144
|
+
logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7145
|
+
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7146
|
+
if (streamBuffer.hasCallback()) {
|
|
7147
|
+
streamBuffer.write(`\u274C **Query execution failed:**
|
|
7148
|
+
\`\`\`
|
|
7149
|
+
${errorMsg}
|
|
7150
|
+
\`\`\`
|
|
7151
|
+
|
|
7152
|
+
`);
|
|
7153
|
+
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7154
|
+
streamBuffer.write(`\u{1F527} **Generating corrected query...**
|
|
7155
|
+
|
|
7156
|
+
`);
|
|
7157
|
+
}
|
|
7158
|
+
}
|
|
7159
|
+
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7162
|
+
/**
|
|
7163
|
+
* Execute an external tool with retry tracking and streaming feedback
|
|
7164
|
+
*/
|
|
7165
|
+
async executeExternalTool(toolName, toolInput, externalTools) {
|
|
7166
|
+
const { streamBuffer, logCollector, providerName } = this.config;
|
|
7167
|
+
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7168
|
+
if (!externalTool) {
|
|
7169
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
7170
|
+
}
|
|
7171
|
+
const attempts = (this.toolAttempts.get(toolName) || 0) + 1;
|
|
7172
|
+
this.toolAttempts.set(toolName, attempts);
|
|
7173
|
+
logger.info(`[${providerName}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
|
|
7174
|
+
logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
|
|
7175
|
+
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7176
|
+
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7177
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
7178
|
+
logCollector?.error(errorMsg);
|
|
7179
|
+
if (streamBuffer.hasCallback()) {
|
|
7180
|
+
streamBuffer.write(`
|
|
7181
|
+
|
|
7182
|
+
\u274C ${errorMsg}
|
|
7183
|
+
|
|
7184
|
+
Please try rephrasing your request or contact support.
|
|
7185
|
+
|
|
7186
|
+
`);
|
|
7187
|
+
}
|
|
7188
|
+
throw new Error(errorMsg);
|
|
7189
|
+
}
|
|
7190
|
+
try {
|
|
7191
|
+
streamBuffer.flush();
|
|
7192
|
+
if (streamBuffer.hasCallback()) {
|
|
7193
|
+
if (attempts === 1) {
|
|
7194
|
+
streamBuffer.write(`
|
|
7195
|
+
|
|
7196
|
+
\u{1F517} **Executing ${externalTool.name}...**
|
|
7197
|
+
|
|
7198
|
+
`);
|
|
7199
|
+
} else {
|
|
7200
|
+
streamBuffer.write(`
|
|
7201
|
+
|
|
7202
|
+
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7203
|
+
|
|
7204
|
+
`);
|
|
7205
|
+
}
|
|
7206
|
+
await streamDelay();
|
|
7207
|
+
}
|
|
7208
|
+
const result = await withProgressHeartbeat(
|
|
7209
|
+
() => externalTool.fn(toolInput),
|
|
7210
|
+
`Running ${externalTool.name}`,
|
|
7211
|
+
streamBuffer
|
|
7212
|
+
);
|
|
7213
|
+
logger.info(`[${providerName}] External tool ${externalTool.name} executed successfully`);
|
|
7214
|
+
logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
|
|
7215
|
+
if (!this.executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7216
|
+
const formattedForTracking = formatToolResultForLLM(result, {
|
|
7217
|
+
toolName: externalTool.name,
|
|
7218
|
+
toolLimit: externalTool.limit,
|
|
7219
|
+
maxRows: TOOL_TRACKING_MAX_ROWS,
|
|
7220
|
+
maxCharsPerField: TOOL_TRACKING_MAX_CHARS
|
|
7221
|
+
});
|
|
7222
|
+
this.executedToolsList.push({
|
|
7223
|
+
id: externalTool.id,
|
|
7224
|
+
name: externalTool.name,
|
|
7225
|
+
params: toolInput,
|
|
7226
|
+
result: {
|
|
7227
|
+
_totalRecords: formattedForTracking.summary.totalRecords,
|
|
7228
|
+
_recordsShown: formattedForTracking.summary.recordsShown,
|
|
7229
|
+
_metadata: formattedForTracking.metadata,
|
|
7230
|
+
_sampleData: formattedForTracking.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS)
|
|
7231
|
+
},
|
|
7232
|
+
outputSchema: externalTool.outputSchema
|
|
7233
|
+
});
|
|
7234
|
+
logger.info(`[${providerName}] Tracked executed tool: ${externalTool.name} with ${formattedForTracking.summary.totalRecords} total records`);
|
|
7235
|
+
}
|
|
7236
|
+
if (streamBuffer.hasCallback()) {
|
|
7237
|
+
streamBuffer.write(`\u2705 **${externalTool.name} completed successfully**
|
|
7238
|
+
|
|
7239
|
+
`);
|
|
7240
|
+
await streamDelay();
|
|
7241
|
+
}
|
|
7242
|
+
const formattedToolResult = formatToolResultForLLM(result, {
|
|
7243
|
+
toolName: externalTool.name,
|
|
7244
|
+
toolLimit: externalTool.limit,
|
|
7245
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7246
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7247
|
+
});
|
|
7248
|
+
logger.info(`[${providerName}] Tool result formatted: ${formattedToolResult.summary.recordsShown}/${formattedToolResult.summary.totalRecords} records`);
|
|
7249
|
+
if (formattedToolResult.truncationNote) {
|
|
7250
|
+
logger.info(`[${providerName}] Truncation: ${formattedToolResult.truncationNote}`);
|
|
7251
|
+
}
|
|
7252
|
+
return formatResultAsString(formattedToolResult);
|
|
7253
|
+
} catch (error) {
|
|
7254
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7255
|
+
logger.error(`[${providerName}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7256
|
+
logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
|
|
7257
|
+
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7258
|
+
if (streamBuffer.hasCallback()) {
|
|
7259
|
+
streamBuffer.write(`\u274C **${externalTool.name} failed:**
|
|
7260
|
+
\`\`\`
|
|
7261
|
+
${errorMsg}
|
|
7262
|
+
\`\`\`
|
|
7263
|
+
|
|
7264
|
+
`);
|
|
7265
|
+
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7266
|
+
streamBuffer.write(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7267
|
+
|
|
7268
|
+
`);
|
|
7269
|
+
}
|
|
7270
|
+
}
|
|
7271
|
+
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
};
|
|
7275
|
+
|
|
7276
|
+
// src/userResponse/base-llm.ts
|
|
7277
|
+
var BaseLLM = class {
|
|
7278
|
+
constructor(config) {
|
|
7279
|
+
this.model = config?.model || this.getDefaultModel();
|
|
7280
|
+
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
7281
|
+
this.defaultLimit = config?.defaultLimit || 10;
|
|
7282
|
+
this.apiKey = config?.apiKey;
|
|
7283
|
+
this.modelStrategy = config?.modelStrategy || "fast";
|
|
7284
|
+
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7285
|
+
this.queryService = new QueryExecutionService({
|
|
7286
|
+
defaultLimit: this.defaultLimit,
|
|
7287
|
+
getModelForTask: (taskType) => this.getModelForTask(taskType),
|
|
7288
|
+
getApiKey: (apiKey) => this.getApiKey(apiKey),
|
|
7289
|
+
providerName: this.getProviderName()
|
|
7290
|
+
});
|
|
7291
|
+
}
|
|
7292
|
+
/**
|
|
7293
|
+
* Get the appropriate model based on task type and model strategy
|
|
7294
|
+
* @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
|
|
7295
|
+
* @returns The model string to use for this task
|
|
7296
|
+
*/
|
|
7297
|
+
getModelForTask(taskType) {
|
|
7298
|
+
switch (this.modelStrategy) {
|
|
7299
|
+
case "best":
|
|
7300
|
+
return this.model;
|
|
7301
|
+
case "fast":
|
|
7302
|
+
return this.fastModel;
|
|
7303
|
+
case "balanced":
|
|
7304
|
+
default:
|
|
7305
|
+
return taskType === "complex" ? this.model : this.fastModel;
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
/**
|
|
7309
|
+
* Set the model strategy at runtime
|
|
7310
|
+
* @param strategy - 'best', 'fast', or 'balanced'
|
|
7311
|
+
*/
|
|
7312
|
+
setModelStrategy(strategy) {
|
|
7313
|
+
this.modelStrategy = strategy;
|
|
7314
|
+
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
7315
|
+
}
|
|
7316
|
+
/**
|
|
7317
|
+
* Get the current model strategy
|
|
7318
|
+
* @returns The current model strategy
|
|
7319
|
+
*/
|
|
7320
|
+
getModelStrategy() {
|
|
7321
|
+
return this.modelStrategy;
|
|
7322
|
+
}
|
|
7323
|
+
/**
|
|
7324
|
+
* Set the conversation similarity threshold at runtime
|
|
7325
|
+
* @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
|
|
7326
|
+
*/
|
|
7327
|
+
setConversationSimilarityThreshold(threshold) {
|
|
7328
|
+
if (threshold < 0 || threshold > 1) {
|
|
7329
|
+
logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default ${DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD}`);
|
|
7330
|
+
this.conversationSimilarityThreshold = DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
this.conversationSimilarityThreshold = threshold;
|
|
7334
|
+
logger.info(`[${this.getProviderName()}] Conversation similarity threshold set to: ${threshold}`);
|
|
7335
|
+
}
|
|
7336
|
+
/**
|
|
7337
|
+
* Get the current conversation similarity threshold
|
|
7338
|
+
* @returns The current threshold value
|
|
7339
|
+
*/
|
|
7340
|
+
getConversationSimilarityThreshold() {
|
|
7341
|
+
return this.conversationSimilarityThreshold;
|
|
7342
|
+
}
|
|
7343
|
+
/**
|
|
7344
|
+
* Get the API key (from instance, parameter, or environment)
|
|
7345
|
+
*/
|
|
7346
|
+
getApiKey(apiKey) {
|
|
7347
|
+
return apiKey || this.apiKey || this.getDefaultApiKey();
|
|
7348
|
+
}
|
|
7349
|
+
/**
|
|
7350
|
+
* Check if a component contains a Form (data_modification component)
|
|
7351
|
+
* Forms have hardcoded defaultValues that become stale when cached
|
|
7352
|
+
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
7353
|
+
*/
|
|
7354
|
+
containsFormComponent(component) {
|
|
7355
|
+
if (!component) return false;
|
|
7356
|
+
if (component.type === "Form" || component.name === "DynamicForm") {
|
|
7357
|
+
return true;
|
|
7358
|
+
}
|
|
7359
|
+
if (component.type === "Container" || component.name === "MultiComponentContainer") {
|
|
7360
|
+
const nestedComponents = component.props?.config?.components || [];
|
|
7361
|
+
for (const nested of nestedComponents) {
|
|
7362
|
+
if (nested.type === "Form" || nested.name === "DynamicForm") {
|
|
7363
|
+
return true;
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
return false;
|
|
7368
|
+
}
|
|
7369
|
+
/**
|
|
7370
|
+
* Match components from text response suggestions and generate follow-up questions
|
|
7371
|
+
* Takes a text response with component suggestions (c1:type format) and matches with available components
|
|
7372
|
+
* Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
|
|
7373
|
+
* All components are placed in a default MultiComponentContainer layout
|
|
7374
|
+
* @param analysisContent - The text response containing component suggestions
|
|
7375
|
+
* @param components - List of available components
|
|
7376
|
+
* @param apiKey - Optional API key
|
|
7377
|
+
* @param logCollector - Optional log collector
|
|
7378
|
+
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
7379
|
+
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
7380
|
+
*/
|
|
7381
|
+
async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, logCollector, componentStreamCallback, deferredTools, executedTools, collections, userId) {
|
|
7382
|
+
const methodStartTime = Date.now();
|
|
7383
|
+
const methodName = "matchComponentsFromAnalysis";
|
|
7384
|
+
logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
|
|
7385
|
+
try {
|
|
7386
|
+
logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
|
|
7387
|
+
let availableComponentsText = "No components available";
|
|
7388
|
+
if (components && components.length > 0) {
|
|
7389
|
+
availableComponentsText = components.map((comp, idx) => {
|
|
7390
|
+
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
7391
|
+
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
7392
|
+
return `${idx + 1}. ID: ${comp.id}
|
|
7393
|
+
Name: ${comp.name}
|
|
7394
|
+
Type: ${comp.type}
|
|
7395
|
+
Description: ${comp.description || "No description"}
|
|
7396
|
+
Keywords: ${keywords}
|
|
7397
|
+
Props Structure: ${propsPreview}`;
|
|
7398
|
+
}).join("\n\n");
|
|
7399
|
+
}
|
|
7400
|
+
let deferredToolsText = "No deferred external tools for this request.";
|
|
7401
|
+
if (deferredTools && deferredTools.length > 0) {
|
|
7402
|
+
logger.info(`[${this.getProviderName()}] Passing ${deferredTools.length} deferred tools to component matching`);
|
|
7403
|
+
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) => {
|
|
7404
|
+
return `${idx + 1}. **${tool.name}**
|
|
7405
|
+
toolId: "${tool.id}" (USE THIS EXACT VALUE - do not modify!)
|
|
7406
|
+
toolName: "${tool.name}"
|
|
7407
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7408
|
+
requiredFields:
|
|
7409
|
+
${JSON.stringify(tool.requiredFields || [], null, 2)}`;
|
|
7410
|
+
}).join("\n\n");
|
|
7411
|
+
}
|
|
7412
|
+
let executedToolsText = "No external tools were executed for data fetching.";
|
|
7413
|
+
if (executedTools && executedTools.length > 0) {
|
|
7414
|
+
logger.info(`[${this.getProviderName()}] Passing ${executedTools.length} executed tools to component matching`);
|
|
7415
|
+
executedToolsText = "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
|
|
7416
|
+
let outputSchemaText = "Not available";
|
|
7417
|
+
let fieldNamesList = "";
|
|
7418
|
+
const recordCount = tool.result?._totalRecords ?? "unknown";
|
|
7419
|
+
let metadataText = "";
|
|
7420
|
+
if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
|
|
7421
|
+
const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
7422
|
+
metadataText = `
|
|
7423
|
+
\u{1F4CB} METADATA: ${metadataEntries}`;
|
|
7424
|
+
}
|
|
7425
|
+
if (tool.outputSchema) {
|
|
7426
|
+
const fields = tool.outputSchema.fields || [];
|
|
7427
|
+
const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
|
|
7428
|
+
const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
|
|
7429
|
+
fieldNamesList = `
|
|
7430
|
+
\u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
|
|
7431
|
+
\u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
|
|
7432
|
+
const fieldsText = fields.map(
|
|
7433
|
+
(f) => ` "${f.name}" (${f.type}): ${f.description}`
|
|
7434
|
+
).join("\n");
|
|
7435
|
+
outputSchemaText = `${tool.outputSchema.description}
|
|
7436
|
+
Fields:
|
|
7437
|
+
${fieldsText}`;
|
|
7438
|
+
}
|
|
7439
|
+
return `${idx + 1}. **${tool.name}**
|
|
7440
|
+
toolId: "${tool.id}"
|
|
7441
|
+
toolName: "${tool.name}"
|
|
7442
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7443
|
+
recordCount: ${recordCount} rows returned${metadataText}
|
|
7444
|
+
outputSchema: ${outputSchemaText}${fieldNamesList}`;
|
|
7445
|
+
}).join("\n\n");
|
|
7446
|
+
}
|
|
7447
|
+
const schemaDoc = schema.generateSchemaDocumentation();
|
|
6278
7448
|
const databaseRules = await promptLoader.loadDatabaseRules();
|
|
6279
7449
|
let knowledgeBaseContext = "No additional knowledge base context available.";
|
|
6280
7450
|
if (collections) {
|
|
@@ -6282,7 +7452,7 @@ ${fieldsText}`;
|
|
|
6282
7452
|
prompt: userPrompt || analysisContent,
|
|
6283
7453
|
collections,
|
|
6284
7454
|
userId,
|
|
6285
|
-
topK:
|
|
7455
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
6286
7456
|
});
|
|
6287
7457
|
knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
|
|
6288
7458
|
}
|
|
@@ -6298,22 +7468,6 @@ ${fieldsText}`;
|
|
|
6298
7468
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6299
7469
|
});
|
|
6300
7470
|
logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
|
|
6301
|
-
const extractPromptText = (content) => {
|
|
6302
|
-
if (typeof content === "string") return content;
|
|
6303
|
-
if (Array.isArray(content)) {
|
|
6304
|
-
return content.map((item) => {
|
|
6305
|
-
if (typeof item === "string") return item;
|
|
6306
|
-
if (item && typeof item.text === "string") return item.text;
|
|
6307
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
6308
|
-
return JSON.stringify(item, null, 2);
|
|
6309
|
-
}).join("\n\n---\n\n");
|
|
6310
|
-
}
|
|
6311
|
-
if (content && typeof content === "object") {
|
|
6312
|
-
if (typeof content.text === "string") return content.text;
|
|
6313
|
-
return JSON.stringify(content, null, 2);
|
|
6314
|
-
}
|
|
6315
|
-
return String(content);
|
|
6316
|
-
};
|
|
6317
7471
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "system", extractPromptText(prompts.system));
|
|
6318
7472
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "user", `Text Analysis:
|
|
6319
7473
|
${analysisContent}
|
|
@@ -6390,23 +7544,32 @@ ${executedToolsText}`);
|
|
|
6390
7544
|
{ componentName: answerComponent.name, componentType: answerComponent.type, reasoning: answerComponentData.reasoning }
|
|
6391
7545
|
);
|
|
6392
7546
|
}
|
|
6393
|
-
|
|
7547
|
+
let answerQuery = answerComponent.props?.query;
|
|
6394
7548
|
logger.info(`[${this.getProviderName()}] Answer component detected: ${answerComponent.name} (${answerComponent.type}), hasQuery: ${!!answerQuery}, hasDbExecute: ${!!collections?.["database"]?.["execute"]}`);
|
|
7549
|
+
if (answerQuery) {
|
|
7550
|
+
if (typeof answerQuery === "string") {
|
|
7551
|
+
answerQuery = ensureQueryLimit(answerQuery, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7552
|
+
} else if (answerQuery?.sql) {
|
|
7553
|
+
const queryObj = answerQuery;
|
|
7554
|
+
answerQuery = { ...queryObj, sql: ensureQueryLimit(queryObj.sql, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT) };
|
|
7555
|
+
}
|
|
7556
|
+
answerComponent.props.query = answerQuery;
|
|
7557
|
+
}
|
|
6395
7558
|
if (answerQuery && collections?.["database"]?.["execute"]) {
|
|
6396
7559
|
(async () => {
|
|
6397
|
-
const
|
|
7560
|
+
const maxRetries = MAX_QUERY_VALIDATION_RETRIES;
|
|
6398
7561
|
let attempts = 0;
|
|
6399
7562
|
let validated = false;
|
|
6400
7563
|
let currentQuery = answerQuery;
|
|
6401
7564
|
let currentQueryStr = typeof answerQuery === "string" ? answerQuery : answerQuery?.sql || "";
|
|
6402
7565
|
let lastError = "";
|
|
6403
7566
|
logger.info(`[${this.getProviderName()}] Validating answer component query before streaming...`);
|
|
6404
|
-
while (attempts <
|
|
7567
|
+
while (attempts < maxRetries && !validated) {
|
|
6405
7568
|
attempts++;
|
|
6406
7569
|
try {
|
|
6407
|
-
const cacheKey = this.getQueryCacheKey(currentQuery);
|
|
7570
|
+
const cacheKey = this.queryService.getQueryCacheKey(currentQuery);
|
|
6408
7571
|
if (cacheKey) {
|
|
6409
|
-
logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${
|
|
7572
|
+
logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${maxRetries}`);
|
|
6410
7573
|
const result2 = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6411
7574
|
queryCache.set(cacheKey, result2);
|
|
6412
7575
|
validated = true;
|
|
@@ -6421,11 +7584,11 @@ ${executedToolsText}`);
|
|
|
6421
7584
|
}
|
|
6422
7585
|
} catch (validationError) {
|
|
6423
7586
|
lastError = validationError instanceof Error ? validationError.message : String(validationError);
|
|
6424
|
-
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${
|
|
6425
|
-
if (attempts <
|
|
7587
|
+
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${maxRetries}): ${lastError}`);
|
|
7588
|
+
if (attempts < maxRetries) {
|
|
6426
7589
|
try {
|
|
6427
7590
|
logger.info(`[${this.getProviderName()}] Requesting LLM to fix answer component query...`);
|
|
6428
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
7591
|
+
const fixedQueryStr = await this.queryService.requestQueryFix(
|
|
6429
7592
|
currentQueryStr,
|
|
6430
7593
|
lastError,
|
|
6431
7594
|
{
|
|
@@ -6435,7 +7598,7 @@ ${executedToolsText}`);
|
|
|
6435
7598
|
},
|
|
6436
7599
|
apiKey
|
|
6437
7600
|
);
|
|
6438
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit,
|
|
7601
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6439
7602
|
if (typeof currentQuery === "string") {
|
|
6440
7603
|
currentQuery = limitedFixedQuery;
|
|
6441
7604
|
} else {
|
|
@@ -6476,7 +7639,7 @@ ${executedToolsText}`);
|
|
|
6476
7639
|
},
|
|
6477
7640
|
{
|
|
6478
7641
|
model: this.getModelForTask("complex"),
|
|
6479
|
-
maxTokens:
|
|
7642
|
+
maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
|
|
6480
7643
|
temperature: 0,
|
|
6481
7644
|
apiKey: this.getApiKey(apiKey),
|
|
6482
7645
|
partial: partialCallback
|
|
@@ -6512,137 +7675,14 @@ ${executedToolsText}`);
|
|
|
6512
7675
|
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
6513
7676
|
return null;
|
|
6514
7677
|
}
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
logger.warn(`[${this.getProviderName()}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
6522
|
-
cleanedProps.externalTool = null;
|
|
6523
|
-
} else {
|
|
6524
|
-
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6525
|
-
if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6526
|
-
const validFieldNames = executedTool.outputSchema.fields.map((f) => f.name);
|
|
6527
|
-
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6528
|
-
const findMatchingField = (fieldName, configKey) => {
|
|
6529
|
-
if (!fieldName) return null;
|
|
6530
|
-
const lowerField = fieldName.toLowerCase();
|
|
6531
|
-
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6532
|
-
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6533
|
-
const containsMatches = validFieldNames.filter(
|
|
6534
|
-
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6535
|
-
);
|
|
6536
|
-
if (containsMatches.length === 1) return containsMatches[0];
|
|
6537
|
-
const fieldTypes = executedTool.outputSchema.fields.reduce((acc, f) => {
|
|
6538
|
-
acc[f.name] = f.type;
|
|
6539
|
-
return acc;
|
|
6540
|
-
}, {});
|
|
6541
|
-
const numericConfigKeys = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6542
|
-
const stringConfigKeys = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6543
|
-
if (numericConfigKeys.includes(configKey)) {
|
|
6544
|
-
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6545
|
-
const match = numericFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
|
|
6546
|
-
if (match) return match;
|
|
6547
|
-
if (numericFields.length > 0) {
|
|
6548
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6549
|
-
return numericFields[0];
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
|
-
if (stringConfigKeys.includes(configKey)) {
|
|
6553
|
-
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6554
|
-
const match = stringFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
|
|
6555
|
-
if (match) return match;
|
|
6556
|
-
if (stringFields.length > 0) {
|
|
6557
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6558
|
-
return stringFields[0];
|
|
6559
|
-
}
|
|
6560
|
-
}
|
|
6561
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6562
|
-
return validFieldNames[0];
|
|
6563
|
-
};
|
|
6564
|
-
const configFieldsToValidate = [
|
|
6565
|
-
"xAxisKey",
|
|
6566
|
-
"yAxisKey",
|
|
6567
|
-
"valueKey",
|
|
6568
|
-
"nameKey",
|
|
6569
|
-
"labelKey",
|
|
6570
|
-
"groupBy",
|
|
6571
|
-
"aggregationField",
|
|
6572
|
-
"seriesKey",
|
|
6573
|
-
"sizeKey",
|
|
6574
|
-
"xAggregationField",
|
|
6575
|
-
"yAggregationField"
|
|
6576
|
-
];
|
|
6577
|
-
for (const configKey of configFieldsToValidate) {
|
|
6578
|
-
const fieldValue = cleanedProps.config[configKey];
|
|
6579
|
-
if (fieldValue && typeof fieldValue === "string") {
|
|
6580
|
-
if (!validFieldNames.includes(fieldValue)) {
|
|
6581
|
-
const correctedField = findMatchingField(fieldValue, configKey);
|
|
6582
|
-
if (correctedField) {
|
|
6583
|
-
logger.warn(`[${this.getProviderName()}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6584
|
-
cleanedProps.config[configKey] = correctedField;
|
|
6585
|
-
}
|
|
6586
|
-
}
|
|
6587
|
-
}
|
|
6588
|
-
}
|
|
6589
|
-
if (Array.isArray(cleanedProps.config.series)) {
|
|
6590
|
-
cleanedProps.config.series = cleanedProps.config.series.map((s) => {
|
|
6591
|
-
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6592
|
-
const correctedField = findMatchingField(s.dataKey, "yAxisKey");
|
|
6593
|
-
if (correctedField) {
|
|
6594
|
-
logger.warn(`[${this.getProviderName()}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6595
|
-
return { ...s, dataKey: correctedField };
|
|
6596
|
-
}
|
|
6597
|
-
}
|
|
6598
|
-
return s;
|
|
6599
|
-
});
|
|
6600
|
-
}
|
|
6601
|
-
}
|
|
6602
|
-
}
|
|
6603
|
-
}
|
|
6604
|
-
if (cleanedProps.query) {
|
|
6605
|
-
const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
|
|
6606
|
-
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6607
|
-
logger.warn(`[${this.getProviderName()}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6608
|
-
cleanedProps.query = null;
|
|
6609
|
-
}
|
|
6610
|
-
}
|
|
6611
|
-
if (cleanedProps.query) {
|
|
6612
|
-
const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
|
|
6613
|
-
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6614
|
-
if (fixed) {
|
|
6615
|
-
logger.warn(`[${this.getProviderName()}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6616
|
-
if (typeof cleanedProps.query === "string") {
|
|
6617
|
-
cleanedProps.query = fixedQuery;
|
|
6618
|
-
} else if (cleanedProps.query?.sql) {
|
|
6619
|
-
cleanedProps.query.sql = fixedQuery;
|
|
6620
|
-
}
|
|
6621
|
-
}
|
|
6622
|
-
}
|
|
6623
|
-
if (cleanedProps.query) {
|
|
6624
|
-
if (typeof cleanedProps.query === "string") {
|
|
6625
|
-
cleanedProps.query = ensureQueryLimit(
|
|
6626
|
-
cleanedProps.query,
|
|
6627
|
-
this.defaultLimit,
|
|
6628
|
-
10
|
|
6629
|
-
// maxLimit - enforce maximum of 10 rows for component queries
|
|
6630
|
-
);
|
|
6631
|
-
} else if (cleanedProps.query?.sql) {
|
|
6632
|
-
cleanedProps.query = {
|
|
6633
|
-
...cleanedProps.query,
|
|
6634
|
-
sql: ensureQueryLimit(
|
|
6635
|
-
cleanedProps.query.sql,
|
|
6636
|
-
this.defaultLimit,
|
|
6637
|
-
10
|
|
6638
|
-
// maxLimit - enforce maximum of 10 rows for component queries
|
|
6639
|
-
)
|
|
6640
|
-
};
|
|
7678
|
+
const cleanedProps = processComponentProps(
|
|
7679
|
+
mc.props,
|
|
7680
|
+
executedTools,
|
|
7681
|
+
{
|
|
7682
|
+
providerName: this.getProviderName(),
|
|
7683
|
+
defaultLimit: this.defaultLimit
|
|
6641
7684
|
}
|
|
6642
|
-
|
|
6643
|
-
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6644
|
-
logger.info(`[${this.getProviderName()}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
6645
|
-
}
|
|
7685
|
+
);
|
|
6646
7686
|
return {
|
|
6647
7687
|
...originalComponent,
|
|
6648
7688
|
props: {
|
|
@@ -6656,7 +7696,7 @@ ${executedToolsText}`);
|
|
|
6656
7696
|
logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
|
|
6657
7697
|
logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
|
|
6658
7698
|
try {
|
|
6659
|
-
const validationResult = await this.
|
|
7699
|
+
const validationResult = await this.queryService.validateComponentQueries(
|
|
6660
7700
|
finalComponents,
|
|
6661
7701
|
collections,
|
|
6662
7702
|
apiKey,
|
|
@@ -6696,153 +7736,6 @@ ${executedToolsText}`);
|
|
|
6696
7736
|
};
|
|
6697
7737
|
}
|
|
6698
7738
|
}
|
|
6699
|
-
/**
|
|
6700
|
-
* Validate a single component's query with retry logic
|
|
6701
|
-
* @param component - The component to validate
|
|
6702
|
-
* @param collections - Collections object containing database execute function
|
|
6703
|
-
* @param apiKey - Optional API key for LLM calls
|
|
6704
|
-
* @param logCollector - Optional log collector for logging
|
|
6705
|
-
* @returns Object with validated component (or null if failed) and query result
|
|
6706
|
-
*/
|
|
6707
|
-
async validateSingleComponentQuery(component, collections, apiKey, logCollector) {
|
|
6708
|
-
const MAX_RETRIES = 3;
|
|
6709
|
-
const query = component.props?.query;
|
|
6710
|
-
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6711
|
-
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6712
|
-
let finalQueryKey = originalQueryKey;
|
|
6713
|
-
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6714
|
-
let currentQueryStr = queryStr;
|
|
6715
|
-
let validated = false;
|
|
6716
|
-
let lastError = "";
|
|
6717
|
-
let result = null;
|
|
6718
|
-
let attempts = 0;
|
|
6719
|
-
logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
|
|
6720
|
-
while (attempts < MAX_RETRIES && !validated) {
|
|
6721
|
-
attempts++;
|
|
6722
|
-
try {
|
|
6723
|
-
logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
|
|
6724
|
-
const validationResult = await this.executeQueryForValidation(currentQuery, collections);
|
|
6725
|
-
result = validationResult.result;
|
|
6726
|
-
validated = true;
|
|
6727
|
-
queryCache.set(validationResult.cacheKey, result);
|
|
6728
|
-
logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6729
|
-
logCollector?.info(`\u2713 Query validated for ${component.name}`);
|
|
6730
|
-
if (currentQueryStr !== queryStr) {
|
|
6731
|
-
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6732
|
-
component.props = {
|
|
6733
|
-
...component.props,
|
|
6734
|
-
query: fixedQuery
|
|
6735
|
-
};
|
|
6736
|
-
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6737
|
-
logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
|
|
6738
|
-
}
|
|
6739
|
-
} catch (error) {
|
|
6740
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
6741
|
-
logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
|
|
6742
|
-
logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
|
|
6743
|
-
if (attempts >= MAX_RETRIES) {
|
|
6744
|
-
logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6745
|
-
logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
|
|
6746
|
-
break;
|
|
6747
|
-
}
|
|
6748
|
-
logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
|
|
6749
|
-
logCollector?.info(`Requesting query fix for ${component.name}...`);
|
|
6750
|
-
try {
|
|
6751
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
6752
|
-
currentQueryStr,
|
|
6753
|
-
lastError,
|
|
6754
|
-
{
|
|
6755
|
-
name: component.name,
|
|
6756
|
-
type: component.type,
|
|
6757
|
-
title: component.props?.title
|
|
6758
|
-
},
|
|
6759
|
-
apiKey
|
|
6760
|
-
);
|
|
6761
|
-
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6762
|
-
logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
|
|
6763
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
|
|
6764
|
-
currentQueryStr = limitedFixedQuery;
|
|
6765
|
-
if (typeof currentQuery === "string") {
|
|
6766
|
-
currentQuery = limitedFixedQuery;
|
|
6767
|
-
} else {
|
|
6768
|
-
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6769
|
-
}
|
|
6770
|
-
} else {
|
|
6771
|
-
logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
|
|
6772
|
-
break;
|
|
6773
|
-
}
|
|
6774
|
-
} catch (fixError) {
|
|
6775
|
-
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6776
|
-
logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6777
|
-
break;
|
|
6778
|
-
}
|
|
6779
|
-
}
|
|
6780
|
-
}
|
|
6781
|
-
if (!validated) {
|
|
6782
|
-
logger.warn(`[${this.getProviderName()}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6783
|
-
logCollector?.warn(`Component ${component.name} excluded from response`);
|
|
6784
|
-
}
|
|
6785
|
-
return {
|
|
6786
|
-
component: validated ? component : null,
|
|
6787
|
-
queryKey: finalQueryKey,
|
|
6788
|
-
result,
|
|
6789
|
-
validated
|
|
6790
|
-
};
|
|
6791
|
-
}
|
|
6792
|
-
/**
|
|
6793
|
-
* Validate component queries against the database and retry with LLM fixes if they fail
|
|
6794
|
-
* Uses parallel execution for faster validation
|
|
6795
|
-
* @param components - Array of components with potential queries
|
|
6796
|
-
* @param collections - Collections object containing database execute function
|
|
6797
|
-
* @param apiKey - Optional API key for LLM calls
|
|
6798
|
-
* @param logCollector - Optional log collector for logging
|
|
6799
|
-
* @returns Object with validated components and a map of query results
|
|
6800
|
-
*/
|
|
6801
|
-
async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
|
|
6802
|
-
const queryResults = /* @__PURE__ */ new Map();
|
|
6803
|
-
const validatedComponents = [];
|
|
6804
|
-
const componentsWithoutQuery = [];
|
|
6805
|
-
const componentsWithQuery = [];
|
|
6806
|
-
for (const component of components) {
|
|
6807
|
-
if (!component.props?.query) {
|
|
6808
|
-
componentsWithoutQuery.push(component);
|
|
6809
|
-
} else {
|
|
6810
|
-
componentsWithQuery.push(component);
|
|
6811
|
-
}
|
|
6812
|
-
}
|
|
6813
|
-
validatedComponents.push(...componentsWithoutQuery);
|
|
6814
|
-
if (componentsWithQuery.length === 0) {
|
|
6815
|
-
return { components: validatedComponents, queryResults };
|
|
6816
|
-
}
|
|
6817
|
-
logger.info(`[${this.getProviderName()}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6818
|
-
logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6819
|
-
const validationPromises = componentsWithQuery.map(
|
|
6820
|
-
(component) => this.validateSingleComponentQuery(component, collections, apiKey, logCollector)
|
|
6821
|
-
);
|
|
6822
|
-
const results = await Promise.allSettled(validationPromises);
|
|
6823
|
-
for (let i = 0; i < results.length; i++) {
|
|
6824
|
-
const result = results[i];
|
|
6825
|
-
const component = componentsWithQuery[i];
|
|
6826
|
-
if (result.status === "fulfilled") {
|
|
6827
|
-
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6828
|
-
if (validated && validatedComponent) {
|
|
6829
|
-
validatedComponents.push(validatedComponent);
|
|
6830
|
-
if (queryResult) {
|
|
6831
|
-
queryResults.set(queryKey, queryResult);
|
|
6832
|
-
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6833
|
-
}
|
|
6834
|
-
}
|
|
6835
|
-
} else {
|
|
6836
|
-
logger.error(`[${this.getProviderName()}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6837
|
-
logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6838
|
-
}
|
|
6839
|
-
}
|
|
6840
|
-
logger.info(`[${this.getProviderName()}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6841
|
-
return {
|
|
6842
|
-
components: validatedComponents,
|
|
6843
|
-
queryResults
|
|
6844
|
-
};
|
|
6845
|
-
}
|
|
6846
7739
|
/**
|
|
6847
7740
|
* Classify user question into category and detect external tools needed
|
|
6848
7741
|
* Determines if question is for data analysis, requires external tools, or needs text response
|
|
@@ -6867,24 +7760,8 @@ ${executedToolsText}`);
|
|
|
6867
7760
|
SCHEMA_DOC: schemaDoc || "No database schema available",
|
|
6868
7761
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6869
7762
|
});
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
if (Array.isArray(content)) {
|
|
6873
|
-
return content.map((item) => {
|
|
6874
|
-
if (typeof item === "string") return item;
|
|
6875
|
-
if (item && typeof item.text === "string") return item.text;
|
|
6876
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
6877
|
-
return JSON.stringify(item, null, 2);
|
|
6878
|
-
}).join("\n\n---\n\n");
|
|
6879
|
-
}
|
|
6880
|
-
if (content && typeof content === "object") {
|
|
6881
|
-
if (typeof content.text === "string") return content.text;
|
|
6882
|
-
return JSON.stringify(content, null, 2);
|
|
6883
|
-
}
|
|
6884
|
-
return String(content);
|
|
6885
|
-
};
|
|
6886
|
-
logger.logLLMPrompt("classifyQuestionCategory", "system", extractTextContent(prompts.system));
|
|
6887
|
-
logger.logLLMPrompt("classifyQuestionCategory", "user", extractTextContent(prompts.user));
|
|
7763
|
+
logger.logLLMPrompt("classifyQuestionCategory", "system", extractPromptText(prompts.system));
|
|
7764
|
+
logger.logLLMPrompt("classifyQuestionCategory", "user", extractPromptText(prompts.user));
|
|
6888
7765
|
const result = await LLM.stream(
|
|
6889
7766
|
{
|
|
6890
7767
|
sys: prompts.system,
|
|
@@ -6892,7 +7769,7 @@ ${executedToolsText}`);
|
|
|
6892
7769
|
},
|
|
6893
7770
|
{
|
|
6894
7771
|
model: this.getModelForTask("simple"),
|
|
6895
|
-
maxTokens:
|
|
7772
|
+
maxTokens: MAX_TOKENS_CLASSIFICATION,
|
|
6896
7773
|
temperature: 0,
|
|
6897
7774
|
apiKey: this.getApiKey(apiKey)
|
|
6898
7775
|
},
|
|
@@ -6965,7 +7842,7 @@ ${executedToolsText}`);
|
|
|
6965
7842
|
},
|
|
6966
7843
|
{
|
|
6967
7844
|
model: this.getModelForTask("complex"),
|
|
6968
|
-
maxTokens:
|
|
7845
|
+
maxTokens: MAX_TOKENS_ADAPTATION,
|
|
6969
7846
|
temperature: 0,
|
|
6970
7847
|
apiKey: this.getApiKey(apiKey)
|
|
6971
7848
|
},
|
|
@@ -7082,7 +7959,7 @@ ${executedToolsText}`);
|
|
|
7082
7959
|
prompt: userPrompt,
|
|
7083
7960
|
collections,
|
|
7084
7961
|
userId,
|
|
7085
|
-
topK:
|
|
7962
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
7086
7963
|
});
|
|
7087
7964
|
const knowledgeBaseContext = kbResult.combinedContext;
|
|
7088
7965
|
const prompts = await promptLoader.loadPrompts("text-response", {
|
|
@@ -7094,24 +7971,8 @@ ${executedToolsText}`);
|
|
|
7094
7971
|
AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc,
|
|
7095
7972
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
7096
7973
|
});
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
if (Array.isArray(content)) {
|
|
7100
|
-
return content.map((item) => {
|
|
7101
|
-
if (typeof item === "string") return item;
|
|
7102
|
-
if (item && typeof item.text === "string") return item.text;
|
|
7103
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
7104
|
-
return JSON.stringify(item, null, 2);
|
|
7105
|
-
}).join("\n\n---\n\n");
|
|
7106
|
-
}
|
|
7107
|
-
if (content && typeof content === "object") {
|
|
7108
|
-
if (typeof content.text === "string") return content.text;
|
|
7109
|
-
return JSON.stringify(content, null, 2);
|
|
7110
|
-
}
|
|
7111
|
-
return String(content);
|
|
7112
|
-
};
|
|
7113
|
-
logger.logLLMPrompt("generateTextResponse", "system", extractText(prompts.system));
|
|
7114
|
-
logger.logLLMPrompt("generateTextResponse", "user", extractText(prompts.user));
|
|
7974
|
+
logger.logLLMPrompt("generateTextResponse", "system", extractPromptText(prompts.system));
|
|
7975
|
+
logger.logLLMPrompt("generateTextResponse", "user", extractPromptText(prompts.user));
|
|
7115
7976
|
logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
|
|
7116
7977
|
logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
|
|
7117
7978
|
logCollector?.info("Generating text response with query execution capability...");
|
|
@@ -7223,324 +8084,24 @@ ${executedToolsText}`);
|
|
|
7223
8084
|
logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
|
|
7224
8085
|
logger.info(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
|
|
7225
8086
|
}
|
|
7226
|
-
const
|
|
7227
|
-
const
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
};
|
|
7243
|
-
const
|
|
7244
|
-
fullStreamedText += chunk;
|
|
7245
|
-
streamBuffer += chunk;
|
|
7246
|
-
if (chunk.includes("\n") || chunk.length > 100) {
|
|
7247
|
-
if (flushTimer) {
|
|
7248
|
-
clearTimeout(flushTimer);
|
|
7249
|
-
flushTimer = null;
|
|
7250
|
-
}
|
|
7251
|
-
flushStreamBuffer();
|
|
7252
|
-
} else if (!flushTimer) {
|
|
7253
|
-
flushTimer = setTimeout(flushStreamBuffer, FLUSH_INTERVAL);
|
|
7254
|
-
}
|
|
7255
|
-
} : void 0;
|
|
7256
|
-
const flushStream = () => {
|
|
7257
|
-
if (flushTimer) {
|
|
7258
|
-
clearTimeout(flushTimer);
|
|
7259
|
-
flushTimer = null;
|
|
7260
|
-
}
|
|
7261
|
-
flushStreamBuffer();
|
|
7262
|
-
};
|
|
7263
|
-
const streamDelay = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7264
|
-
const withProgressHeartbeat = async (operation, progressMessage, intervalMs = 1e3) => {
|
|
7265
|
-
if (!wrappedStreamCallback) {
|
|
7266
|
-
return operation();
|
|
7267
|
-
}
|
|
7268
|
-
const startTime = Date.now();
|
|
7269
|
-
await streamDelay(30);
|
|
7270
|
-
wrappedStreamCallback(`\u23F3 ${progressMessage}`);
|
|
7271
|
-
const heartbeatInterval = setInterval(() => {
|
|
7272
|
-
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
7273
|
-
if (elapsedSeconds >= 1) {
|
|
7274
|
-
wrappedStreamCallback(` (${elapsedSeconds}s)`);
|
|
7275
|
-
}
|
|
7276
|
-
}, intervalMs);
|
|
7277
|
-
try {
|
|
7278
|
-
const result2 = await operation();
|
|
7279
|
-
return result2;
|
|
7280
|
-
} finally {
|
|
7281
|
-
clearInterval(heartbeatInterval);
|
|
7282
|
-
wrappedStreamCallback("\n\n");
|
|
7283
|
-
}
|
|
7284
|
-
};
|
|
7285
|
-
const toolHandler = async (toolName, toolInput) => {
|
|
7286
|
-
if (toolName === "execute_query") {
|
|
7287
|
-
let sql = toolInput.sql;
|
|
7288
|
-
const params = toolInput.params || {};
|
|
7289
|
-
const reasoning = toolInput.reasoning;
|
|
7290
|
-
sql = ensureQueryLimit(sql, 10, 10);
|
|
7291
|
-
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
7292
|
-
const attempts = (queryAttempts.get(queryKey) || 0) + 1;
|
|
7293
|
-
queryAttempts.set(queryKey, attempts);
|
|
7294
|
-
logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
|
|
7295
|
-
if (Object.keys(params).length > 0) {
|
|
7296
|
-
logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
|
|
7297
|
-
}
|
|
7298
|
-
if (reasoning) {
|
|
7299
|
-
logCollector?.info(`Query reasoning: ${reasoning}`);
|
|
7300
|
-
}
|
|
7301
|
-
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
7302
|
-
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
7303
|
-
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
7304
|
-
logCollector?.error(errorMsg);
|
|
7305
|
-
maxAttemptsReached = true;
|
|
7306
|
-
if (wrappedStreamCallback) {
|
|
7307
|
-
wrappedStreamCallback(`
|
|
7308
|
-
|
|
7309
|
-
\u274C ${errorMsg}
|
|
7310
|
-
|
|
7311
|
-
Please try rephrasing your question or simplifying your request.
|
|
7312
|
-
|
|
7313
|
-
`);
|
|
7314
|
-
}
|
|
7315
|
-
throw new Error(errorMsg);
|
|
7316
|
-
}
|
|
7317
|
-
try {
|
|
7318
|
-
flushStream();
|
|
7319
|
-
if (wrappedStreamCallback) {
|
|
7320
|
-
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
7321
|
-
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
7322
|
-
if (attempts === 1) {
|
|
7323
|
-
wrappedStreamCallback(`
|
|
7324
|
-
|
|
7325
|
-
\u{1F50D} **Analyzing your question...**
|
|
7326
|
-
|
|
7327
|
-
`);
|
|
7328
|
-
await streamDelay(50);
|
|
7329
|
-
if (reasoning) {
|
|
7330
|
-
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
7331
|
-
|
|
7332
|
-
`);
|
|
7333
|
-
await streamDelay(50);
|
|
7334
|
-
}
|
|
7335
|
-
wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
|
|
7336
|
-
\`\`\`sql
|
|
7337
|
-
${sql}
|
|
7338
|
-
\`\`\`${paramsDisplay}
|
|
7339
|
-
|
|
7340
|
-
`);
|
|
7341
|
-
await streamDelay(50);
|
|
7342
|
-
} else {
|
|
7343
|
-
wrappedStreamCallback(`
|
|
7344
|
-
|
|
7345
|
-
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
7346
|
-
|
|
7347
|
-
`);
|
|
7348
|
-
await streamDelay(50);
|
|
7349
|
-
if (reasoning) {
|
|
7350
|
-
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
7351
|
-
|
|
7352
|
-
`);
|
|
7353
|
-
await streamDelay(50);
|
|
7354
|
-
}
|
|
7355
|
-
wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
|
|
7356
|
-
\`\`\`sql
|
|
7357
|
-
${sql}
|
|
7358
|
-
\`\`\`${paramsDisplay}
|
|
7359
|
-
|
|
7360
|
-
`);
|
|
7361
|
-
await streamDelay(50);
|
|
7362
|
-
}
|
|
7363
|
-
}
|
|
7364
|
-
logCollector?.logQuery(
|
|
7365
|
-
`Executing SQL query (attempt ${attempts})`,
|
|
7366
|
-
{ sql, params },
|
|
7367
|
-
{ reasoning, attempt: attempts }
|
|
7368
|
-
);
|
|
7369
|
-
if (!collections || !collections["database"] || !collections["database"]["execute"]) {
|
|
7370
|
-
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
7371
|
-
}
|
|
7372
|
-
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
7373
|
-
const result2 = await withProgressHeartbeat(
|
|
7374
|
-
() => collections["database"]["execute"](queryPayload),
|
|
7375
|
-
"Executing database query",
|
|
7376
|
-
800
|
|
7377
|
-
// Send heartbeat every 800ms for responsive feedback
|
|
7378
|
-
);
|
|
7379
|
-
const data = result2?.data || result2;
|
|
7380
|
-
const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7381
|
-
logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
|
|
7382
|
-
logCollector?.info(`Query successful, returned ${rowCount} rows`);
|
|
7383
|
-
if (wrappedStreamCallback) {
|
|
7384
|
-
wrappedStreamCallback(`
|
|
7385
|
-
\u2705 **Query executed successfully!**
|
|
7386
|
-
|
|
7387
|
-
`);
|
|
7388
|
-
await streamDelay(50);
|
|
7389
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
7390
|
-
const firstRow = data[0];
|
|
7391
|
-
const columns = Object.keys(firstRow);
|
|
7392
|
-
if (data.length === 1 && columns.length === 1) {
|
|
7393
|
-
const value = firstRow[columns[0]];
|
|
7394
|
-
wrappedStreamCallback(`**Result:** ${value}
|
|
7395
|
-
|
|
7396
|
-
`);
|
|
7397
|
-
await streamDelay(50);
|
|
7398
|
-
} else if (data.length > 0) {
|
|
7399
|
-
wrappedStreamCallback(`**Retrieved ${rowCount} rows**
|
|
7400
|
-
|
|
7401
|
-
`);
|
|
7402
|
-
await streamDelay(50);
|
|
7403
|
-
wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
|
|
7404
|
-
|
|
7405
|
-
`);
|
|
7406
|
-
await streamDelay(50);
|
|
7407
|
-
}
|
|
7408
|
-
} else if (Array.isArray(data) && data.length === 0) {
|
|
7409
|
-
wrappedStreamCallback(`**No rows returned.**
|
|
7410
|
-
|
|
7411
|
-
`);
|
|
7412
|
-
await streamDelay(50);
|
|
7413
|
-
}
|
|
7414
|
-
wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
|
|
7415
|
-
|
|
7416
|
-
`);
|
|
7417
|
-
}
|
|
7418
|
-
return JSON.stringify(data, null, 2);
|
|
7419
|
-
} catch (error) {
|
|
7420
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7421
|
-
logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7422
|
-
logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7423
|
-
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7424
|
-
if (wrappedStreamCallback) {
|
|
7425
|
-
wrappedStreamCallback(`\u274C **Query execution failed:**
|
|
7426
|
-
\`\`\`
|
|
7427
|
-
${errorMsg}
|
|
7428
|
-
\`\`\`
|
|
7429
|
-
|
|
7430
|
-
`);
|
|
7431
|
-
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7432
|
-
wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
|
|
7433
|
-
|
|
7434
|
-
`);
|
|
7435
|
-
}
|
|
7436
|
-
}
|
|
7437
|
-
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7438
|
-
}
|
|
7439
|
-
} else {
|
|
7440
|
-
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7441
|
-
if (externalTool) {
|
|
7442
|
-
const attempts = (toolAttempts.get(toolName) || 0) + 1;
|
|
7443
|
-
toolAttempts.set(toolName, attempts);
|
|
7444
|
-
logger.info(`[${this.getProviderName()}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
|
|
7445
|
-
logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
|
|
7446
|
-
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7447
|
-
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7448
|
-
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
7449
|
-
logCollector?.error(errorMsg);
|
|
7450
|
-
if (wrappedStreamCallback) {
|
|
7451
|
-
wrappedStreamCallback(`
|
|
7452
|
-
|
|
7453
|
-
\u274C ${errorMsg}
|
|
7454
|
-
|
|
7455
|
-
Please try rephrasing your request or contact support.
|
|
7456
|
-
|
|
7457
|
-
`);
|
|
7458
|
-
}
|
|
7459
|
-
throw new Error(errorMsg);
|
|
7460
|
-
}
|
|
7461
|
-
try {
|
|
7462
|
-
flushStream();
|
|
7463
|
-
if (wrappedStreamCallback) {
|
|
7464
|
-
if (attempts === 1) {
|
|
7465
|
-
wrappedStreamCallback(`
|
|
7466
|
-
|
|
7467
|
-
\u{1F517} **Executing ${externalTool.name}...**
|
|
7468
|
-
|
|
7469
|
-
`);
|
|
7470
|
-
} else {
|
|
7471
|
-
wrappedStreamCallback(`
|
|
7472
|
-
|
|
7473
|
-
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7474
|
-
|
|
7475
|
-
`);
|
|
7476
|
-
}
|
|
7477
|
-
await streamDelay(50);
|
|
7478
|
-
}
|
|
7479
|
-
const result2 = await withProgressHeartbeat(
|
|
7480
|
-
() => externalTool.fn(toolInput),
|
|
7481
|
-
`Running ${externalTool.name}`,
|
|
7482
|
-
800
|
|
7483
|
-
// Send heartbeat every 800ms
|
|
7484
|
-
);
|
|
7485
|
-
logger.info(`[${this.getProviderName()}] External tool ${externalTool.name} executed successfully`);
|
|
7486
|
-
logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
|
|
7487
|
-
if (!executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7488
|
-
let resultSummary = null;
|
|
7489
|
-
if (result2) {
|
|
7490
|
-
const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2);
|
|
7491
|
-
if (resultStr.length > 1e3) {
|
|
7492
|
-
resultSummary = {
|
|
7493
|
-
_preview: resultStr.substring(0, 1e3) + "... (truncated)",
|
|
7494
|
-
_totalLength: resultStr.length,
|
|
7495
|
-
_recordCount: Array.isArray(result2) ? result2.length : result2?.data?.length || result2?.contacts?.length || result2?.salesorders?.length || "unknown"
|
|
7496
|
-
};
|
|
7497
|
-
} else {
|
|
7498
|
-
resultSummary = result2;
|
|
7499
|
-
}
|
|
7500
|
-
}
|
|
7501
|
-
executedToolsList.push({
|
|
7502
|
-
id: externalTool.id,
|
|
7503
|
-
name: externalTool.name,
|
|
7504
|
-
params: toolInput,
|
|
7505
|
-
// The actual parameters used in this execution
|
|
7506
|
-
result: resultSummary,
|
|
7507
|
-
// Store summary instead of full result to save memory
|
|
7508
|
-
outputSchema: externalTool.outputSchema
|
|
7509
|
-
// Include output schema for component config generation
|
|
7510
|
-
});
|
|
7511
|
-
logger.info(`[${this.getProviderName()}] Tracked executed tool: ${externalTool.name} with params: ${JSON.stringify(toolInput)}`);
|
|
7512
|
-
}
|
|
7513
|
-
if (wrappedStreamCallback) {
|
|
7514
|
-
wrappedStreamCallback(`\u2705 **${externalTool.name} completed successfully**
|
|
7515
|
-
|
|
7516
|
-
`);
|
|
7517
|
-
await streamDelay(50);
|
|
7518
|
-
}
|
|
7519
|
-
return JSON.stringify(result2, null, 2);
|
|
7520
|
-
} catch (error) {
|
|
7521
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7522
|
-
logger.error(`[${this.getProviderName()}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7523
|
-
logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
|
|
7524
|
-
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7525
|
-
if (wrappedStreamCallback) {
|
|
7526
|
-
wrappedStreamCallback(`\u274C **${externalTool.name} failed:**
|
|
7527
|
-
\`\`\`
|
|
7528
|
-
${errorMsg}
|
|
7529
|
-
\`\`\`
|
|
7530
|
-
|
|
7531
|
-
`);
|
|
7532
|
-
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7533
|
-
wrappedStreamCallback(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7534
|
-
|
|
7535
|
-
`);
|
|
7536
|
-
}
|
|
7537
|
-
}
|
|
7538
|
-
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7539
|
-
}
|
|
7540
|
-
}
|
|
7541
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
7542
|
-
}
|
|
7543
|
-
};
|
|
8087
|
+
const streamBuffer = new StreamBuffer(streamCallback);
|
|
8088
|
+
const toolExecutor = new ToolExecutorService({
|
|
8089
|
+
providerName: this.getProviderName(),
|
|
8090
|
+
collections,
|
|
8091
|
+
streamBuffer,
|
|
8092
|
+
logCollector
|
|
8093
|
+
});
|
|
8094
|
+
const executableExternalTools = externalTools?.map((t) => ({
|
|
8095
|
+
id: t.id,
|
|
8096
|
+
name: t.name,
|
|
8097
|
+
description: t.description,
|
|
8098
|
+
fn: t.fn,
|
|
8099
|
+
limit: t.limit,
|
|
8100
|
+
outputSchema: t.outputSchema,
|
|
8101
|
+
executionType: t.executionType,
|
|
8102
|
+
userProvidedData: t.userProvidedData
|
|
8103
|
+
})) || [];
|
|
8104
|
+
const toolHandler = toolExecutor.createToolHandler(executableExternalTools);
|
|
7544
8105
|
const result = await LLM.streamWithTools(
|
|
7545
8106
|
{
|
|
7546
8107
|
sys: prompts.system,
|
|
@@ -7550,18 +8111,16 @@ ${errorMsg}
|
|
|
7550
8111
|
toolHandler,
|
|
7551
8112
|
{
|
|
7552
8113
|
model: this.getModelForTask("complex"),
|
|
7553
|
-
maxTokens:
|
|
8114
|
+
maxTokens: MAX_TOKENS_TEXT_RESPONSE,
|
|
7554
8115
|
temperature: 0,
|
|
7555
8116
|
apiKey: this.getApiKey(apiKey),
|
|
7556
|
-
partial:
|
|
7557
|
-
// Pass the wrapped streaming callback to LLM
|
|
8117
|
+
partial: streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
|
|
7558
8118
|
},
|
|
7559
|
-
|
|
7560
|
-
// max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
|
|
8119
|
+
MAX_TOOL_CALLING_ITERATIONS
|
|
7561
8120
|
);
|
|
7562
8121
|
logger.info(`[${this.getProviderName()}] Text response stream completed`);
|
|
7563
|
-
const textResponse =
|
|
7564
|
-
if (
|
|
8122
|
+
const textResponse = streamBuffer.getFullText() || result || "I apologize, but I was unable to generate a response.";
|
|
8123
|
+
if (toolExecutor.isMaxAttemptsReached()) {
|
|
7565
8124
|
const methodDuration2 = Date.now() - methodStartTime;
|
|
7566
8125
|
logger.warn(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: max attempts reached`);
|
|
7567
8126
|
logCollector?.error("Failed to generate valid query after maximum attempts");
|
|
@@ -7584,10 +8143,10 @@ ${errorMsg}
|
|
|
7584
8143
|
textLength: textResponse.length
|
|
7585
8144
|
}
|
|
7586
8145
|
);
|
|
7587
|
-
|
|
7588
|
-
if (
|
|
7589
|
-
|
|
7590
|
-
|
|
8146
|
+
streamBuffer.flush();
|
|
8147
|
+
if (streamBuffer.hasCallback() && components && components.length > 0 && category !== "general") {
|
|
8148
|
+
streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
|
|
8149
|
+
streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
|
|
7591
8150
|
}
|
|
7592
8151
|
let matchedComponents = [];
|
|
7593
8152
|
let layoutTitle = "Dashboard";
|
|
@@ -7613,11 +8172,11 @@ ${errorMsg}
|
|
|
7613
8172
|
logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions for general question`);
|
|
7614
8173
|
} else if (components && components.length > 0) {
|
|
7615
8174
|
logger.info(`[${this.getProviderName()}] Matching components from text response...`);
|
|
7616
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback setup:
|
|
7617
|
-
const componentStreamCallback =
|
|
8175
|
+
logger.info(`[${this.getProviderName()}] componentStreamCallback setup: hasCallback=${streamBuffer.hasCallback()}, category=${category}`);
|
|
8176
|
+
const componentStreamCallback = streamBuffer.hasCallback() && category !== "data_modification" ? (component) => {
|
|
7618
8177
|
logger.info(`[${this.getProviderName()}] componentStreamCallback INVOKED for: ${component.name} (${component.type})`);
|
|
7619
8178
|
const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
|
|
7620
|
-
|
|
8179
|
+
streamBuffer.write(answerMarker);
|
|
7621
8180
|
logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
|
|
7622
8181
|
} : void 0;
|
|
7623
8182
|
logger.info(`[${this.getProviderName()}] componentStreamCallback created: ${!!componentStreamCallback}`);
|
|
@@ -7644,7 +8203,7 @@ ${errorMsg}
|
|
|
7644
8203
|
logCollector,
|
|
7645
8204
|
componentStreamCallback,
|
|
7646
8205
|
deferredTools,
|
|
7647
|
-
|
|
8206
|
+
toolExecutor.getExecutedTools(),
|
|
7648
8207
|
collections,
|
|
7649
8208
|
userId
|
|
7650
8209
|
);
|
|
@@ -7728,7 +8287,7 @@ ${errorMsg}
|
|
|
7728
8287
|
userPrompt,
|
|
7729
8288
|
collections,
|
|
7730
8289
|
userId,
|
|
7731
|
-
similarityThreshold:
|
|
8290
|
+
similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
|
|
7732
8291
|
});
|
|
7733
8292
|
if (conversationMatch) {
|
|
7734
8293
|
logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
|
|
@@ -7743,7 +8302,7 @@ ${errorMsg}
|
|
|
7743
8302
|
logger.info(`[${this.getProviderName()}] Skipping cached result - Form components contain stale defaultValues, fetching fresh data`);
|
|
7744
8303
|
logCollector?.info("Skipping cache for form - fetching current values from database...");
|
|
7745
8304
|
} else if (!component) {
|
|
7746
|
-
if (conversationMatch.similarity >=
|
|
8305
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7747
8306
|
const elapsedTime2 = Date.now() - startTime;
|
|
7748
8307
|
logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response`);
|
|
7749
8308
|
logCollector?.info(`\u2713 Exact match for general question - returning cached response`);
|
|
@@ -7765,7 +8324,7 @@ ${errorMsg}
|
|
|
7765
8324
|
logCollector?.info("Similar match found but was a general conversation - processing as new question");
|
|
7766
8325
|
}
|
|
7767
8326
|
} else {
|
|
7768
|
-
if (conversationMatch.similarity >=
|
|
8327
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7769
8328
|
const elapsedTime2 = Date.now() - startTime;
|
|
7770
8329
|
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
|
|
7771
8330
|
logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
|
|
@@ -7960,7 +8519,7 @@ ${errorMsg}
|
|
|
7960
8519
|
},
|
|
7961
8520
|
{
|
|
7962
8521
|
model: this.getModelForTask("simple"),
|
|
7963
|
-
maxTokens:
|
|
8522
|
+
maxTokens: MAX_TOKENS_NEXT_QUESTIONS,
|
|
7964
8523
|
temperature: 0,
|
|
7965
8524
|
apiKey: this.getApiKey(apiKey)
|
|
7966
8525
|
},
|
|
@@ -8741,6 +9300,12 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8741
9300
|
};
|
|
8742
9301
|
async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools) {
|
|
8743
9302
|
const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools);
|
|
9303
|
+
if (response.data?.component?.props?.config?.components) {
|
|
9304
|
+
response.data.component.props.config.components = response.data.component.props.config.components.map((comp) => ({
|
|
9305
|
+
...comp,
|
|
9306
|
+
id: `comp_${Math.random().toString(36).substring(2, 8)}`
|
|
9307
|
+
}));
|
|
9308
|
+
}
|
|
8744
9309
|
sendDataResponse4(
|
|
8745
9310
|
response.id || data.id,
|
|
8746
9311
|
{
|