@superatomai/sdk-node 0.0.71 → 0.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +942 -942
- package/dist/index.d.mts +94 -51
- package/dist/index.d.ts +94 -51
- package/dist/index.js +1666 -1384
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1676 -1393
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,7 +34,6 @@ __export(index_exports, {
|
|
|
34
34
|
CONTEXT_CONFIG: () => CONTEXT_CONFIG,
|
|
35
35
|
CleanupService: () => CleanupService,
|
|
36
36
|
LLM: () => LLM,
|
|
37
|
-
SDK_VERSION: () => SDK_VERSION,
|
|
38
37
|
STORAGE_CONFIG: () => STORAGE_CONFIG,
|
|
39
38
|
SuperatomSDK: () => SuperatomSDK,
|
|
40
39
|
Thread: () => Thread,
|
|
@@ -1516,6 +1515,351 @@ var QueryCache = class {
|
|
|
1516
1515
|
};
|
|
1517
1516
|
var queryCache = new QueryCache();
|
|
1518
1517
|
|
|
1518
|
+
// src/userResponse/llm-result-truncator.ts
|
|
1519
|
+
var DEFAULT_MAX_ROWS = 10;
|
|
1520
|
+
var DEFAULT_MAX_CHARS_PER_FIELD = 500;
|
|
1521
|
+
function inferFieldType(value) {
|
|
1522
|
+
if (value === null || value === void 0) {
|
|
1523
|
+
return "null";
|
|
1524
|
+
}
|
|
1525
|
+
if (typeof value === "string") {
|
|
1526
|
+
if (isDateString(value)) {
|
|
1527
|
+
return "date";
|
|
1528
|
+
}
|
|
1529
|
+
return "string";
|
|
1530
|
+
}
|
|
1531
|
+
if (typeof value === "number") {
|
|
1532
|
+
return "number";
|
|
1533
|
+
}
|
|
1534
|
+
if (typeof value === "boolean") {
|
|
1535
|
+
return "boolean";
|
|
1536
|
+
}
|
|
1537
|
+
if (Array.isArray(value)) {
|
|
1538
|
+
return "array";
|
|
1539
|
+
}
|
|
1540
|
+
if (typeof value === "object") {
|
|
1541
|
+
return "object";
|
|
1542
|
+
}
|
|
1543
|
+
return "unknown";
|
|
1544
|
+
}
|
|
1545
|
+
function isDateString(value) {
|
|
1546
|
+
const datePatterns = [
|
|
1547
|
+
/^\d{4}-\d{2}-\d{2}$/,
|
|
1548
|
+
// YYYY-MM-DD
|
|
1549
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
|
|
1550
|
+
// ISO 8601
|
|
1551
|
+
/^\d{2}\/\d{2}\/\d{4}$/,
|
|
1552
|
+
// MM/DD/YYYY
|
|
1553
|
+
/^\d{4}\/\d{2}\/\d{2}$/
|
|
1554
|
+
// YYYY/MM/DD
|
|
1555
|
+
];
|
|
1556
|
+
return datePatterns.some((pattern) => pattern.test(value));
|
|
1557
|
+
}
|
|
1558
|
+
function truncateTextField(value, maxLength) {
|
|
1559
|
+
if (value.length <= maxLength) {
|
|
1560
|
+
return { text: value, wasTruncated: false };
|
|
1561
|
+
}
|
|
1562
|
+
const truncated = value.substring(0, maxLength);
|
|
1563
|
+
const remaining = value.length - maxLength;
|
|
1564
|
+
return {
|
|
1565
|
+
text: `${truncated}... (${remaining} more chars)`,
|
|
1566
|
+
wasTruncated: true
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function truncateFieldValue(value, maxCharsPerField) {
|
|
1570
|
+
if (value === null || value === void 0) {
|
|
1571
|
+
return { value, wasTruncated: false };
|
|
1572
|
+
}
|
|
1573
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1574
|
+
return { value, wasTruncated: false };
|
|
1575
|
+
}
|
|
1576
|
+
if (typeof value === "string") {
|
|
1577
|
+
const result2 = truncateTextField(value, maxCharsPerField);
|
|
1578
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1579
|
+
}
|
|
1580
|
+
if (Array.isArray(value)) {
|
|
1581
|
+
if (value.length === 0) {
|
|
1582
|
+
return { value: [], wasTruncated: false };
|
|
1583
|
+
}
|
|
1584
|
+
const preview = value.slice(0, 3);
|
|
1585
|
+
const hasMore = value.length > 3;
|
|
1586
|
+
return {
|
|
1587
|
+
value: hasMore ? `[${preview.join(", ")}... (${value.length} items)]` : value,
|
|
1588
|
+
wasTruncated: hasMore
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
if (typeof value === "object") {
|
|
1592
|
+
const jsonStr = JSON.stringify(value);
|
|
1593
|
+
const result2 = truncateTextField(jsonStr, maxCharsPerField);
|
|
1594
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1595
|
+
}
|
|
1596
|
+
const strValue = String(value);
|
|
1597
|
+
const result = truncateTextField(strValue, maxCharsPerField);
|
|
1598
|
+
return { value: result.text, wasTruncated: result.wasTruncated };
|
|
1599
|
+
}
|
|
1600
|
+
function truncateRow(row, maxCharsPerField) {
|
|
1601
|
+
const truncatedRow = {};
|
|
1602
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1603
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1604
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1605
|
+
truncatedRow[key] = result.value;
|
|
1606
|
+
if (result.wasTruncated) {
|
|
1607
|
+
truncatedFields.add(key);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return { row: truncatedRow, truncatedFields };
|
|
1611
|
+
}
|
|
1612
|
+
function extractMetadataFromObject(obj, dataKey, maxCharsPerField) {
|
|
1613
|
+
const metadata = {};
|
|
1614
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1615
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1616
|
+
if (key === dataKey) {
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
if (Array.isArray(value)) {
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1623
|
+
metadata[key] = result.value;
|
|
1624
|
+
if (result.wasTruncated) {
|
|
1625
|
+
truncatedFields.add(key);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return { metadata, truncatedFields };
|
|
1629
|
+
}
|
|
1630
|
+
function extractSchema(data, truncatedFields = /* @__PURE__ */ new Set()) {
|
|
1631
|
+
if (!data || data.length === 0) {
|
|
1632
|
+
return [];
|
|
1633
|
+
}
|
|
1634
|
+
const firstRow = data[0];
|
|
1635
|
+
const schema2 = [];
|
|
1636
|
+
for (const [name, value] of Object.entries(firstRow)) {
|
|
1637
|
+
schema2.push({
|
|
1638
|
+
name,
|
|
1639
|
+
type: inferFieldType(value),
|
|
1640
|
+
truncated: truncatedFields.has(name) ? true : void 0
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
return schema2;
|
|
1644
|
+
}
|
|
1645
|
+
function truncateDataArray(data, options = {}) {
|
|
1646
|
+
const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1647
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1648
|
+
if (!data || !Array.isArray(data)) {
|
|
1649
|
+
return {
|
|
1650
|
+
data: [],
|
|
1651
|
+
totalRecords: 0,
|
|
1652
|
+
recordsShown: 0,
|
|
1653
|
+
truncatedFields: /* @__PURE__ */ new Set()
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
const totalRecords = data.length;
|
|
1657
|
+
const rowsToProcess = data.slice(0, maxRows);
|
|
1658
|
+
const truncatedData = [];
|
|
1659
|
+
const allTruncatedFields = /* @__PURE__ */ new Set();
|
|
1660
|
+
for (const row of rowsToProcess) {
|
|
1661
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(row, maxCharsPerField);
|
|
1662
|
+
truncatedData.push(truncatedRow);
|
|
1663
|
+
for (const field of truncatedFields) {
|
|
1664
|
+
allTruncatedFields.add(field);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
data: truncatedData,
|
|
1669
|
+
totalRecords,
|
|
1670
|
+
recordsShown: truncatedData.length,
|
|
1671
|
+
truncatedFields: allTruncatedFields
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
function buildTruncationNote(totalRecords, recordsShown, truncatedFields, maxCharsPerField, sourceName) {
|
|
1675
|
+
const parts = [];
|
|
1676
|
+
if (totalRecords > recordsShown) {
|
|
1677
|
+
const source = sourceName ? ` from ${sourceName}` : "";
|
|
1678
|
+
parts.push(`Showing ${recordsShown} of ${totalRecords} total records${source}`);
|
|
1679
|
+
}
|
|
1680
|
+
if (truncatedFields.size > 0) {
|
|
1681
|
+
const fieldList = Array.from(truncatedFields).join(", ");
|
|
1682
|
+
parts.push(`Fields truncated to ${maxCharsPerField} chars: ${fieldList}`);
|
|
1683
|
+
}
|
|
1684
|
+
return parts.length > 0 ? parts.join(". ") + "." : null;
|
|
1685
|
+
}
|
|
1686
|
+
function formatQueryResultForLLM(data, options = {}) {
|
|
1687
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1688
|
+
if (!Array.isArray(data)) {
|
|
1689
|
+
if (data !== null && data !== void 0) {
|
|
1690
|
+
return {
|
|
1691
|
+
summary: {
|
|
1692
|
+
totalRecords: 1,
|
|
1693
|
+
recordsShown: 1,
|
|
1694
|
+
schema: [{ name: "result", type: inferFieldType(data) }]
|
|
1695
|
+
},
|
|
1696
|
+
data: [{ result: data }],
|
|
1697
|
+
truncationNote: null
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
return {
|
|
1701
|
+
summary: {
|
|
1702
|
+
totalRecords: 0,
|
|
1703
|
+
recordsShown: 0,
|
|
1704
|
+
schema: []
|
|
1705
|
+
},
|
|
1706
|
+
data: [],
|
|
1707
|
+
truncationNote: null
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
const {
|
|
1711
|
+
data: truncatedData,
|
|
1712
|
+
totalRecords,
|
|
1713
|
+
recordsShown,
|
|
1714
|
+
truncatedFields
|
|
1715
|
+
} = truncateDataArray(data, options);
|
|
1716
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1717
|
+
const truncationNote = buildTruncationNote(
|
|
1718
|
+
totalRecords,
|
|
1719
|
+
recordsShown,
|
|
1720
|
+
truncatedFields,
|
|
1721
|
+
maxCharsPerField,
|
|
1722
|
+
"query"
|
|
1723
|
+
);
|
|
1724
|
+
return {
|
|
1725
|
+
summary: {
|
|
1726
|
+
totalRecords,
|
|
1727
|
+
recordsShown,
|
|
1728
|
+
schema: schema2
|
|
1729
|
+
},
|
|
1730
|
+
data: truncatedData,
|
|
1731
|
+
truncationNote
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
function formatToolResultForLLM(result, options = {}) {
|
|
1735
|
+
const { toolName, toolLimit } = options;
|
|
1736
|
+
const effectiveMaxRows = toolLimit ?? options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1737
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1738
|
+
if (result === null || result === void 0) {
|
|
1739
|
+
return {
|
|
1740
|
+
toolName,
|
|
1741
|
+
summary: {
|
|
1742
|
+
totalRecords: 0,
|
|
1743
|
+
recordsShown: 0,
|
|
1744
|
+
schema: []
|
|
1745
|
+
},
|
|
1746
|
+
data: [],
|
|
1747
|
+
truncationNote: null
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
if (typeof result === "string") {
|
|
1751
|
+
const { text, wasTruncated } = truncateTextField(result, maxCharsPerField);
|
|
1752
|
+
return {
|
|
1753
|
+
toolName,
|
|
1754
|
+
summary: {
|
|
1755
|
+
totalRecords: 1,
|
|
1756
|
+
recordsShown: 1,
|
|
1757
|
+
schema: [{ name: "result", type: "string", truncated: wasTruncated || void 0 }]
|
|
1758
|
+
},
|
|
1759
|
+
data: [{ result: text }],
|
|
1760
|
+
truncationNote: wasTruncated ? `Result truncated to ${maxCharsPerField} chars.` : null
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
if (Array.isArray(result)) {
|
|
1764
|
+
const {
|
|
1765
|
+
data: truncatedData,
|
|
1766
|
+
totalRecords,
|
|
1767
|
+
recordsShown,
|
|
1768
|
+
truncatedFields
|
|
1769
|
+
} = truncateDataArray(result, {
|
|
1770
|
+
maxRows: effectiveMaxRows,
|
|
1771
|
+
maxCharsPerField
|
|
1772
|
+
});
|
|
1773
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1774
|
+
const truncationNote = buildTruncationNote(
|
|
1775
|
+
totalRecords,
|
|
1776
|
+
recordsShown,
|
|
1777
|
+
truncatedFields,
|
|
1778
|
+
maxCharsPerField,
|
|
1779
|
+
toolName
|
|
1780
|
+
);
|
|
1781
|
+
return {
|
|
1782
|
+
toolName,
|
|
1783
|
+
summary: {
|
|
1784
|
+
totalRecords,
|
|
1785
|
+
recordsShown,
|
|
1786
|
+
schema: schema2
|
|
1787
|
+
},
|
|
1788
|
+
data: truncatedData,
|
|
1789
|
+
truncationNote
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
if (typeof result === "object") {
|
|
1793
|
+
const objResult = result;
|
|
1794
|
+
const dataWrapperKeys = ["data", "results", "items", "records", "rows", "list"];
|
|
1795
|
+
for (const key of dataWrapperKeys) {
|
|
1796
|
+
if (Array.isArray(objResult[key])) {
|
|
1797
|
+
const innerData = objResult[key];
|
|
1798
|
+
const {
|
|
1799
|
+
data: truncatedData,
|
|
1800
|
+
totalRecords,
|
|
1801
|
+
recordsShown,
|
|
1802
|
+
truncatedFields: dataTruncatedFields
|
|
1803
|
+
} = truncateDataArray(innerData, {
|
|
1804
|
+
maxRows: effectiveMaxRows,
|
|
1805
|
+
maxCharsPerField
|
|
1806
|
+
});
|
|
1807
|
+
const {
|
|
1808
|
+
metadata,
|
|
1809
|
+
truncatedFields: metadataTruncatedFields
|
|
1810
|
+
} = extractMetadataFromObject(objResult, key, maxCharsPerField);
|
|
1811
|
+
const allTruncatedFields = /* @__PURE__ */ new Set([...dataTruncatedFields, ...metadataTruncatedFields]);
|
|
1812
|
+
const schema3 = extractSchema(truncatedData, dataTruncatedFields);
|
|
1813
|
+
const truncationNote2 = buildTruncationNote(
|
|
1814
|
+
totalRecords,
|
|
1815
|
+
recordsShown,
|
|
1816
|
+
allTruncatedFields,
|
|
1817
|
+
maxCharsPerField,
|
|
1818
|
+
toolName
|
|
1819
|
+
);
|
|
1820
|
+
const hasMetadata = Object.keys(metadata).length > 0;
|
|
1821
|
+
return {
|
|
1822
|
+
toolName,
|
|
1823
|
+
summary: {
|
|
1824
|
+
totalRecords,
|
|
1825
|
+
recordsShown,
|
|
1826
|
+
schema: schema3
|
|
1827
|
+
},
|
|
1828
|
+
...hasMetadata && { metadata },
|
|
1829
|
+
data: truncatedData,
|
|
1830
|
+
truncationNote: truncationNote2
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(objResult, maxCharsPerField);
|
|
1835
|
+
const schema2 = extractSchema([truncatedRow], truncatedFields);
|
|
1836
|
+
const truncationNote = truncatedFields.size > 0 ? `Fields truncated to ${maxCharsPerField} chars: ${Array.from(truncatedFields).join(", ")}.` : null;
|
|
1837
|
+
return {
|
|
1838
|
+
toolName,
|
|
1839
|
+
summary: {
|
|
1840
|
+
totalRecords: 1,
|
|
1841
|
+
recordsShown: 1,
|
|
1842
|
+
schema: schema2
|
|
1843
|
+
},
|
|
1844
|
+
data: [truncatedRow],
|
|
1845
|
+
truncationNote
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
return {
|
|
1849
|
+
toolName,
|
|
1850
|
+
summary: {
|
|
1851
|
+
totalRecords: 1,
|
|
1852
|
+
recordsShown: 1,
|
|
1853
|
+
schema: [{ name: "result", type: inferFieldType(result) }]
|
|
1854
|
+
},
|
|
1855
|
+
data: [{ result }],
|
|
1856
|
+
truncationNote: null
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function formatResultAsString(formattedResult) {
|
|
1860
|
+
return JSON.stringify(formattedResult, null, 2);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1519
1863
|
// src/handlers/data-request.ts
|
|
1520
1864
|
function getQueryCacheKey(query) {
|
|
1521
1865
|
if (typeof query === "string") {
|
|
@@ -1597,15 +1941,25 @@ async function handleDataRequest(data, collections, sendMessage) {
|
|
|
1597
1941
|
}
|
|
1598
1942
|
}
|
|
1599
1943
|
if (uiBlock) {
|
|
1944
|
+
const formattedResult = formatToolResultForLLM(result, {
|
|
1945
|
+
maxRows: 3,
|
|
1946
|
+
// Only need a few sample rows for summary
|
|
1947
|
+
maxCharsPerField: 100
|
|
1948
|
+
// Short truncation for summary
|
|
1949
|
+
});
|
|
1600
1950
|
const dataSummary = {
|
|
1601
1951
|
_dataReceived: true,
|
|
1602
1952
|
_timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1603
1953
|
_collection: collection,
|
|
1604
1954
|
_operation: op,
|
|
1605
|
-
|
|
1955
|
+
_totalRecords: formattedResult.summary.totalRecords,
|
|
1956
|
+
_recordsShown: formattedResult.summary.recordsShown,
|
|
1957
|
+
_metadata: formattedResult.metadata,
|
|
1958
|
+
// Preserve totalItems, totalDeadstockItems, etc.
|
|
1959
|
+
_schema: formattedResult.summary.schema
|
|
1606
1960
|
};
|
|
1607
1961
|
uiBlock.setComponentData(dataSummary);
|
|
1608
|
-
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (
|
|
1962
|
+
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (${formattedResult.summary.totalRecords} total records)`);
|
|
1609
1963
|
} else {
|
|
1610
1964
|
logger.warn(`UIBlock ${uiBlockId} not found in threads`);
|
|
1611
1965
|
}
|
|
@@ -3013,24 +3367,20 @@ If adaptation is not possible or would fundamentally change the component:
|
|
|
3013
3367
|
"dash-comp-picker": {
|
|
3014
3368
|
system: `You are a component selection expert that picks the best dashboard component and generates complete props based on user requests.
|
|
3015
3369
|
|
|
3370
|
+
## CRITICAL - READ FIRST
|
|
3371
|
+
|
|
3372
|
+
1. Your ENTIRE response must be ONLY a raw JSON object - start with { end with }
|
|
3373
|
+
2. DO NOT explain or answer the user's question in natural language
|
|
3374
|
+
3. DO NOT use markdown code blocks (no \`\`\`)
|
|
3375
|
+
4. DO NOT add any text before or after the JSON
|
|
3376
|
+
5. After executing tools (if needed), return JSON with component selection - NOT a text summary of results
|
|
3377
|
+
|
|
3016
3378
|
## Your Task
|
|
3017
3379
|
|
|
3018
3380
|
Analyze the user's request and:
|
|
3019
3381
|
1. **Select the most appropriate component** from the available components list
|
|
3020
|
-
2. **Determine the data source**: Database query OR External tool
|
|
3021
|
-
3. **Generate complete props** for the selected component
|
|
3022
|
-
|
|
3023
|
-
## Available External Tools
|
|
3024
|
-
|
|
3025
|
-
The following external tools are available:
|
|
3026
|
-
|
|
3027
|
-
{{AVAILABLE_TOOLS}}
|
|
3028
|
-
|
|
3029
|
-
When a tool is needed to complete the user's request:
|
|
3030
|
-
1. **Analyze the request** to determine which tool is needed
|
|
3031
|
-
2. **Extract parameters** from the user's question
|
|
3032
|
-
3. **Execute the tool** by calling it with the extracted parameters
|
|
3033
|
-
4. **Use the results** to configure the component (field names for axes, columns, etc.)
|
|
3382
|
+
2. **Determine the data source**: Database query OR External tool (ERP)
|
|
3383
|
+
3. **Generate complete props** for the selected component including the data retrieval/modification method
|
|
3034
3384
|
|
|
3035
3385
|
## Component Selection Rules
|
|
3036
3386
|
|
|
@@ -3060,7 +3410,7 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3060
3410
|
|
|
3061
3411
|
### Use DATABASE when:
|
|
3062
3412
|
- User asks about data that exists in the database schema
|
|
3063
|
-
- Questions about internal business data
|
|
3413
|
+
- Questions about internal business data
|
|
3064
3414
|
- CRUD operations on database tables
|
|
3065
3415
|
|
|
3066
3416
|
### Use EXTERNAL TOOL when:
|
|
@@ -3073,6 +3423,12 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3073
3423
|
|
|
3074
3424
|
**CRITICAL**: Look at each component's "Props Structure" in the available components list. Generate ALL props that the component expects.
|
|
3075
3425
|
|
|
3426
|
+
**CRITICAL: Each component uses EXACTLY ONE data source - never both!**
|
|
3427
|
+
- If using \`query\`, set \`externalTool: null\`
|
|
3428
|
+
- If using \`externalTool\`, set \`query: null\`
|
|
3429
|
+
- NEVER copy placeholder/description text from component metadata as actual values
|
|
3430
|
+
- \`externalTool.parameters\` MUST be an object, never a string
|
|
3431
|
+
|
|
3076
3432
|
### For Data Viewing Components (charts, tables, KPIs):
|
|
3077
3433
|
|
|
3078
3434
|
**Option A: Database Query** (when data is in database)
|
|
@@ -3081,21 +3437,19 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3081
3437
|
"query": {
|
|
3082
3438
|
"sql": "SELECT column1, column2 FROM table WHERE condition = $param LIMIT 32",
|
|
3083
3439
|
"params": { "param": "value" }
|
|
3084
|
-
}
|
|
3440
|
+
},
|
|
3441
|
+
"externalTool": null
|
|
3085
3442
|
}
|
|
3086
3443
|
\`\`\`
|
|
3087
3444
|
|
|
3088
3445
|
**Option B: External Tool** (when data is from ERP/external system)
|
|
3089
3446
|
\`\`\`json
|
|
3090
3447
|
{
|
|
3448
|
+
"query": null,
|
|
3091
3449
|
"externalTool": {
|
|
3092
3450
|
"toolId": "tool_id_from_list",
|
|
3093
3451
|
"toolName": "Tool Display Name",
|
|
3094
|
-
"
|
|
3095
|
-
"params": {
|
|
3096
|
-
"param1": "value1",
|
|
3097
|
-
"param2": "value2"
|
|
3098
|
-
}
|
|
3452
|
+
"parameters": { "param1": "value1", "param2": "value2" }
|
|
3099
3453
|
}
|
|
3100
3454
|
}
|
|
3101
3455
|
\`\`\`
|
|
@@ -3109,6 +3463,7 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3109
3463
|
"sql": "INSERT INTO table (col1, col2) VALUES ($col1, $col2)",
|
|
3110
3464
|
"params": {}
|
|
3111
3465
|
},
|
|
3466
|
+
"externalTool": null,
|
|
3112
3467
|
"fields": [
|
|
3113
3468
|
{ "name": "col1", "type": "text", "required": true },
|
|
3114
3469
|
{ "name": "col2", "type": "number", "required": false }
|
|
@@ -3116,16 +3471,38 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3116
3471
|
}
|
|
3117
3472
|
\`\`\`
|
|
3118
3473
|
|
|
3474
|
+
For UPDATE:
|
|
3475
|
+
\`\`\`json
|
|
3476
|
+
{
|
|
3477
|
+
"query": {
|
|
3478
|
+
"sql": "UPDATE table SET col1 = $col1, col2 = $col2 WHERE id = $id",
|
|
3479
|
+
"params": { "id": "record_id" }
|
|
3480
|
+
},
|
|
3481
|
+
"externalTool": null
|
|
3482
|
+
}
|
|
3483
|
+
\`\`\`
|
|
3484
|
+
|
|
3485
|
+
For DELETE:
|
|
3486
|
+
\`\`\`json
|
|
3487
|
+
{
|
|
3488
|
+
"query": {
|
|
3489
|
+
"sql": "DELETE FROM table WHERE id = $id",
|
|
3490
|
+
"params": { "id": "record_id" }
|
|
3491
|
+
},
|
|
3492
|
+
"externalTool": null,
|
|
3493
|
+
"submitButtonText": "Confirm Delete",
|
|
3494
|
+
"submitButtonColor": "danger"
|
|
3495
|
+
}
|
|
3496
|
+
\`\`\`
|
|
3497
|
+
|
|
3119
3498
|
**Option B: External Tool Mutation**
|
|
3120
3499
|
\`\`\`json
|
|
3121
3500
|
{
|
|
3501
|
+
"query": null,
|
|
3122
3502
|
"externalTool": {
|
|
3123
3503
|
"toolId": "tool_id_from_list",
|
|
3124
3504
|
"toolName": "Tool Display Name",
|
|
3125
|
-
"
|
|
3126
|
-
"params": {
|
|
3127
|
-
"param1": "value_or_placeholder"
|
|
3128
|
-
}
|
|
3505
|
+
"parameters": { "param1": "value_or_placeholder" }
|
|
3129
3506
|
},
|
|
3130
3507
|
"fields": [
|
|
3131
3508
|
{ "name": "param1", "type": "text", "required": true }
|
|
@@ -3140,6 +3517,7 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3140
3517
|
|
|
3141
3518
|
You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
3142
3519
|
|
|
3520
|
+
\`\`\`json
|
|
3143
3521
|
{
|
|
3144
3522
|
"componentId": "id_from_available_list_or_existing_component_id",
|
|
3145
3523
|
"componentName": "name_of_component",
|
|
@@ -3154,6 +3532,7 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
|
3154
3532
|
// Include all other required props (title, description, config, fields, etc.)
|
|
3155
3533
|
}
|
|
3156
3534
|
}
|
|
3535
|
+
\`\`\`
|
|
3157
3536
|
|
|
3158
3537
|
**CRITICAL:**
|
|
3159
3538
|
- Return ONLY valid JSON (no markdown code blocks, no text before/after)
|
|
@@ -3176,7 +3555,8 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
|
3176
3555
|
|
|
3177
3556
|
---
|
|
3178
3557
|
|
|
3179
|
-
## CONTEXT
|
|
3558
|
+
## CONTEXT
|
|
3559
|
+
`,
|
|
3180
3560
|
user: `{{USER_PROMPT}}`
|
|
3181
3561
|
},
|
|
3182
3562
|
"dash-filter-picker": {
|
|
@@ -3322,9 +3702,7 @@ var PromptLoader = class {
|
|
|
3322
3702
|
this.databaseRulesCache = /* @__PURE__ */ new Map();
|
|
3323
3703
|
this.isInitialized = false;
|
|
3324
3704
|
this.databaseType = "postgresql";
|
|
3325
|
-
logger.debug("Initializing PromptLoader...");
|
|
3326
3705
|
this.promptsDir = config?.promptsDir || import_path.default.join(process.cwd(), ".prompts");
|
|
3327
|
-
logger.debug(`Prompts directory set to: ${this.promptsDir}`);
|
|
3328
3706
|
}
|
|
3329
3707
|
/**
|
|
3330
3708
|
* Load a prompt template from file system OR fallback to hardcoded prompts
|
|
@@ -3338,7 +3716,6 @@ var PromptLoader = class {
|
|
|
3338
3716
|
if (import_fs2.default.existsSync(systemPath) && import_fs2.default.existsSync(userPath)) {
|
|
3339
3717
|
const system = import_fs2.default.readFileSync(systemPath, "utf-8");
|
|
3340
3718
|
const user = import_fs2.default.readFileSync(userPath, "utf-8");
|
|
3341
|
-
logger.info(`\u2713 Loaded prompt '${promptName}' from file system: ${this.promptsDir}`);
|
|
3342
3719
|
return { system, user };
|
|
3343
3720
|
}
|
|
3344
3721
|
} catch (error) {
|
|
@@ -3346,7 +3723,6 @@ var PromptLoader = class {
|
|
|
3346
3723
|
}
|
|
3347
3724
|
const hardcodedPrompt = PROMPTS[promptName];
|
|
3348
3725
|
if (hardcodedPrompt) {
|
|
3349
|
-
logger.info(`\u2713 Loaded prompt '${promptName}' from hardcoded fallback`);
|
|
3350
3726
|
return hardcodedPrompt;
|
|
3351
3727
|
}
|
|
3352
3728
|
throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or hardcoded prompts. Available prompts: ${Object.keys(PROMPTS).join(", ")}`);
|
|
@@ -3360,7 +3736,6 @@ var PromptLoader = class {
|
|
|
3360
3736
|
logger.debug("PromptLoader already initialized, skipping...");
|
|
3361
3737
|
return;
|
|
3362
3738
|
}
|
|
3363
|
-
logger.info("Loading prompts into memory...");
|
|
3364
3739
|
const promptTypes = Object.keys(PROMPTS);
|
|
3365
3740
|
for (const promptName of promptTypes) {
|
|
3366
3741
|
try {
|
|
@@ -3372,7 +3747,6 @@ var PromptLoader = class {
|
|
|
3372
3747
|
}
|
|
3373
3748
|
}
|
|
3374
3749
|
this.isInitialized = true;
|
|
3375
|
-
logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
|
|
3376
3750
|
}
|
|
3377
3751
|
/**
|
|
3378
3752
|
* Replace variables in a template string using {{VARIABLE_NAME}} pattern
|
|
@@ -3412,7 +3786,6 @@ var PromptLoader = class {
|
|
|
3412
3786
|
const processedContext = this.replaceVariables(contextMarker + contextPart, variables);
|
|
3413
3787
|
const staticLength = processedStatic.length;
|
|
3414
3788
|
const contextLength = processedContext.length;
|
|
3415
|
-
logger.debug(`\u2713 Prompt caching enabled for '${promptName}' (cached: ${staticLength} chars, dynamic: ${contextLength} chars)`);
|
|
3416
3789
|
return {
|
|
3417
3790
|
system: [
|
|
3418
3791
|
{
|
|
@@ -3449,7 +3822,6 @@ var PromptLoader = class {
|
|
|
3449
3822
|
this.promptsDir = dir;
|
|
3450
3823
|
this.isInitialized = false;
|
|
3451
3824
|
this.promptCache.clear();
|
|
3452
|
-
logger.debug(`Prompts directory changed to: ${dir}`);
|
|
3453
3825
|
}
|
|
3454
3826
|
/**
|
|
3455
3827
|
* Get current prompts directory
|
|
@@ -3477,7 +3849,6 @@ var PromptLoader = class {
|
|
|
3477
3849
|
setDatabaseType(type) {
|
|
3478
3850
|
this.databaseType = type;
|
|
3479
3851
|
this.databaseRulesCache.clear();
|
|
3480
|
-
logger.debug(`Database type set to: ${type}`);
|
|
3481
3852
|
}
|
|
3482
3853
|
/**
|
|
3483
3854
|
* Get current database type
|
|
@@ -3493,7 +3864,6 @@ var PromptLoader = class {
|
|
|
3493
3864
|
*/
|
|
3494
3865
|
async loadDatabaseRules() {
|
|
3495
3866
|
if (this.databaseRulesCache.has(this.databaseType)) {
|
|
3496
|
-
logger.debug(`\u2713 Database rules for '${this.databaseType}' loaded from cache`);
|
|
3497
3867
|
return this.databaseRulesCache.get(this.databaseType);
|
|
3498
3868
|
}
|
|
3499
3869
|
const rulesPath = import_path.default.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
|
|
@@ -3501,7 +3871,6 @@ var PromptLoader = class {
|
|
|
3501
3871
|
if (import_fs2.default.existsSync(rulesPath)) {
|
|
3502
3872
|
const rules = import_fs2.default.readFileSync(rulesPath, "utf-8");
|
|
3503
3873
|
this.databaseRulesCache.set(this.databaseType, rules);
|
|
3504
|
-
logger.info(`\u2713 Loaded database rules for '${this.databaseType}' from ${rulesPath}`);
|
|
3505
3874
|
return rules;
|
|
3506
3875
|
}
|
|
3507
3876
|
} catch (error) {
|
|
@@ -3777,7 +4146,6 @@ var Schema = class {
|
|
|
3777
4146
|
* @returns Parsed schema object or null if error occurs
|
|
3778
4147
|
*/
|
|
3779
4148
|
getDatabaseSchema() {
|
|
3780
|
-
logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
|
|
3781
4149
|
try {
|
|
3782
4150
|
const dir = import_path2.default.dirname(this.schemaFilePath);
|
|
3783
4151
|
if (!import_fs3.default.existsSync(dir)) {
|
|
@@ -4046,14 +4414,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
|
|
|
4046
4414
|
Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
|
|
4047
4415
|
`;
|
|
4048
4416
|
this.logStream?.write(logLine);
|
|
4049
|
-
if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
|
|
4050
|
-
console.log(`[LLM] \u26A1 CACHE HIT: ${entry.cacheReadTokens.toLocaleString()} tokens read from cache (${entry.method})`);
|
|
4051
|
-
} else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
|
|
4052
|
-
console.log(`[LLM] \u{1F4DD} CACHE WRITE: ${entry.cacheWriteTokens.toLocaleString()} tokens cached for future requests (${entry.method})`);
|
|
4053
|
-
}
|
|
4054
|
-
if (process.env.SUPERATOM_LOG_LEVEL === "verbose") {
|
|
4055
|
-
console.log("\n[LLM-Usage]", logLine);
|
|
4056
|
-
}
|
|
4057
4417
|
}
|
|
4058
4418
|
/**
|
|
4059
4419
|
* Log session summary (call at end of request)
|
|
@@ -4086,11 +4446,6 @@ Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStat
|
|
|
4086
4446
|
|
|
4087
4447
|
`;
|
|
4088
4448
|
this.logStream?.write(summary);
|
|
4089
|
-
console.log("\n[LLM-Usage] Session Summary:");
|
|
4090
|
-
console.log(` Calls: ${this.sessionStats.totalCalls} | Tokens: ${(this.sessionStats.totalInputTokens + this.sessionStats.totalOutputTokens).toLocaleString()} | Cost: $${this.sessionStats.totalCostUSD.toFixed(4)} | Time: ${(this.sessionStats.totalDurationMs / 1e3).toFixed(2)}s`);
|
|
4091
|
-
if (hasCaching) {
|
|
4092
|
-
console.log(` Cache: ${this.sessionStats.totalCacheReadTokens.toLocaleString()} read, ${this.sessionStats.totalCacheWriteTokens.toLocaleString()} written | Savings: ~$${cacheReadSavings.toFixed(4)}`);
|
|
4093
|
-
}
|
|
4094
4449
|
}
|
|
4095
4450
|
/**
|
|
4096
4451
|
* Reset session stats (call at start of new user request)
|
|
@@ -4131,7 +4486,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
|
|
|
4131
4486
|
`;
|
|
4132
4487
|
this.logStream.write(header);
|
|
4133
4488
|
this.resetSession();
|
|
4134
|
-
console.log(`[LLM-Usage] Log file reset for new request: ${this.logPath}`);
|
|
4135
4489
|
} catch (error) {
|
|
4136
4490
|
console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
|
|
4137
4491
|
}
|
|
@@ -5605,21 +5959,20 @@ var getKnowledgeBase = async ({
|
|
|
5605
5959
|
}) => {
|
|
5606
5960
|
try {
|
|
5607
5961
|
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
|
|
5608
|
-
logger.
|
|
5962
|
+
logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
|
|
5609
5963
|
return "";
|
|
5610
5964
|
}
|
|
5611
|
-
logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
|
|
5612
5965
|
const result = await collections["knowledge-base"]["query"]({
|
|
5613
5966
|
prompt,
|
|
5614
5967
|
topK
|
|
5615
5968
|
});
|
|
5616
5969
|
if (!result || !result.content) {
|
|
5617
|
-
logger.
|
|
5970
|
+
logger.warn("[KnowledgeBase] No knowledge base results returned");
|
|
5618
5971
|
return "";
|
|
5619
5972
|
}
|
|
5620
5973
|
logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
|
|
5621
5974
|
if (result.metadata?.sources && result.metadata.sources.length > 0) {
|
|
5622
|
-
logger.
|
|
5975
|
+
logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
|
|
5623
5976
|
}
|
|
5624
5977
|
return result.content;
|
|
5625
5978
|
} catch (error) {
|
|
@@ -5634,13 +5987,12 @@ var getGlobalKnowledgeBase = async ({
|
|
|
5634
5987
|
}) => {
|
|
5635
5988
|
try {
|
|
5636
5989
|
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
|
|
5637
|
-
logger.
|
|
5990
|
+
logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
|
|
5638
5991
|
return "";
|
|
5639
5992
|
}
|
|
5640
|
-
logger.info("[KnowledgeBase] Fetching global knowledge base nodes...");
|
|
5641
5993
|
const result = await collections["knowledge-base"]["getGlobal"]({ limit });
|
|
5642
5994
|
if (!result || !result.content) {
|
|
5643
|
-
logger.
|
|
5995
|
+
logger.warn("[KnowledgeBase] No global knowledge base nodes found");
|
|
5644
5996
|
return "";
|
|
5645
5997
|
}
|
|
5646
5998
|
logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
|
|
@@ -5658,14 +6010,13 @@ var getUserKnowledgeBase = async ({
|
|
|
5658
6010
|
}) => {
|
|
5659
6011
|
try {
|
|
5660
6012
|
if (!userId) {
|
|
5661
|
-
logger.
|
|
6013
|
+
logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
|
|
5662
6014
|
return "";
|
|
5663
6015
|
}
|
|
5664
6016
|
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
|
|
5665
|
-
logger.
|
|
6017
|
+
logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
|
|
5666
6018
|
return "";
|
|
5667
6019
|
}
|
|
5668
|
-
logger.info(`[KnowledgeBase] Fetching user knowledge base nodes for userId: ${userId}...`);
|
|
5669
6020
|
const result = await collections["knowledge-base"]["getByUser"]({
|
|
5670
6021
|
userId: Number(userId),
|
|
5671
6022
|
limit
|
|
@@ -5688,7 +6039,6 @@ var getAllKnowledgeBase = async ({
|
|
|
5688
6039
|
userId,
|
|
5689
6040
|
topK = 3
|
|
5690
6041
|
}) => {
|
|
5691
|
-
logger.info("[KnowledgeBase] Fetching all knowledge base contexts...");
|
|
5692
6042
|
const [globalContext, userContext, queryContext] = await Promise.all([
|
|
5693
6043
|
getGlobalKnowledgeBase({ collections }),
|
|
5694
6044
|
getUserKnowledgeBase({ collections, userId }),
|
|
@@ -5710,7 +6060,6 @@ var getAllKnowledgeBase = async ({
|
|
|
5710
6060
|
combinedContext += "The following information is semantically relevant to the current query:\n\n";
|
|
5711
6061
|
combinedContext += queryContext + "\n\n";
|
|
5712
6062
|
}
|
|
5713
|
-
logger.info(`[KnowledgeBase] Combined knowledge base context: global=${globalContext.length} chars, user=${userContext.length} chars, query=${queryContext.length} chars`);
|
|
5714
6063
|
return {
|
|
5715
6064
|
globalContext,
|
|
5716
6065
|
userContext,
|
|
@@ -5947,11 +6296,11 @@ var searchConversationsWithReranking = async (options) => {
|
|
|
5947
6296
|
} = options;
|
|
5948
6297
|
try {
|
|
5949
6298
|
if (!collections || !collections["conversation-history"]) {
|
|
5950
|
-
logger.
|
|
6299
|
+
logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
|
|
5951
6300
|
return null;
|
|
5952
6301
|
}
|
|
5953
6302
|
if (!collections["conversation-history"]["searchMultiple"]) {
|
|
5954
|
-
logger.
|
|
6303
|
+
logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
|
|
5955
6304
|
return searchConversations({
|
|
5956
6305
|
userPrompt,
|
|
5957
6306
|
collections,
|
|
@@ -5959,9 +6308,6 @@ var searchConversationsWithReranking = async (options) => {
|
|
|
5959
6308
|
similarityThreshold
|
|
5960
6309
|
});
|
|
5961
6310
|
}
|
|
5962
|
-
logger.info(`[ConversationSearch] Hybrid search for: "${userPrompt.substring(0, 50)}..."`);
|
|
5963
|
-
logger.info(`[ConversationSearch] Fetching ${rerankCandidates} candidates for reranking`);
|
|
5964
|
-
logger.info(`[ConversationSearch] Weights - Semantic: ${hybridOptions.semanticWeight}, BM25: ${hybridOptions.bm25Weight}`);
|
|
5965
6311
|
const results = await collections["conversation-history"]["searchMultiple"]({
|
|
5966
6312
|
userPrompt,
|
|
5967
6313
|
userId,
|
|
@@ -6002,7 +6348,6 @@ var searchConversationsWithReranking = async (options) => {
|
|
|
6002
6348
|
logger.info(
|
|
6003
6349
|
`[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
|
|
6004
6350
|
);
|
|
6005
|
-
logger.info(` - Returning cached result for: "${matchedUserPrompt}"`);
|
|
6006
6351
|
return {
|
|
6007
6352
|
uiBlock: best.uiBlock,
|
|
6008
6353
|
similarity: semanticScore,
|
|
@@ -6022,97 +6367,335 @@ var ConversationSearch = {
|
|
|
6022
6367
|
};
|
|
6023
6368
|
var conversation_search_default = ConversationSearch;
|
|
6024
6369
|
|
|
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;
|
|
6370
|
+
// src/userResponse/prompt-extractor.ts
|
|
6371
|
+
function extractPromptText(content) {
|
|
6372
|
+
if (content === null || content === void 0) {
|
|
6373
|
+
return "";
|
|
6034
6374
|
}
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6375
|
+
if (typeof content === "string") {
|
|
6376
|
+
return content;
|
|
6377
|
+
}
|
|
6378
|
+
if (Array.isArray(content)) {
|
|
6379
|
+
return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
|
|
6380
|
+
}
|
|
6381
|
+
if (content && typeof content === "object") {
|
|
6382
|
+
return extractObjectText(content);
|
|
6383
|
+
}
|
|
6384
|
+
return String(content);
|
|
6385
|
+
}
|
|
6386
|
+
function extractContentBlockText(item) {
|
|
6387
|
+
if (typeof item === "string") {
|
|
6388
|
+
return item;
|
|
6389
|
+
}
|
|
6390
|
+
if (item && typeof item === "object") {
|
|
6391
|
+
const obj = item;
|
|
6392
|
+
if (typeof obj.text === "string") {
|
|
6393
|
+
return obj.text;
|
|
6394
|
+
}
|
|
6395
|
+
if (typeof obj.content === "string") {
|
|
6396
|
+
return obj.content;
|
|
6049
6397
|
}
|
|
6398
|
+
return JSON.stringify(item, null, 2);
|
|
6399
|
+
}
|
|
6400
|
+
return String(item);
|
|
6401
|
+
}
|
|
6402
|
+
function extractObjectText(obj) {
|
|
6403
|
+
if (typeof obj.text === "string") {
|
|
6404
|
+
return obj.text;
|
|
6405
|
+
}
|
|
6406
|
+
if (typeof obj.content === "string") {
|
|
6407
|
+
return obj.content;
|
|
6408
|
+
}
|
|
6409
|
+
return JSON.stringify(obj, null, 2);
|
|
6410
|
+
}
|
|
6411
|
+
|
|
6412
|
+
// src/userResponse/constants.ts
|
|
6413
|
+
var MAX_QUERY_VALIDATION_RETRIES = 3;
|
|
6414
|
+
var MAX_QUERY_ATTEMPTS = 6;
|
|
6415
|
+
var MAX_TOOL_ATTEMPTS = 3;
|
|
6416
|
+
var STREAM_FLUSH_INTERVAL_MS = 50;
|
|
6417
|
+
var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
|
|
6418
|
+
var STREAM_DELAY_MS = 50;
|
|
6419
|
+
var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
|
|
6420
|
+
var MAX_TOKENS_QUERY_FIX = 2048;
|
|
6421
|
+
var MAX_TOKENS_COMPONENT_MATCHING = 8192;
|
|
6422
|
+
var MAX_TOKENS_CLASSIFICATION = 1500;
|
|
6423
|
+
var MAX_TOKENS_ADAPTATION = 8192;
|
|
6424
|
+
var MAX_TOKENS_TEXT_RESPONSE = 4e3;
|
|
6425
|
+
var MAX_TOKENS_NEXT_QUESTIONS = 1200;
|
|
6426
|
+
var DEFAULT_MAX_ROWS_FOR_LLM = 10;
|
|
6427
|
+
var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
|
|
6428
|
+
var STREAM_PREVIEW_MAX_ROWS = 10;
|
|
6429
|
+
var STREAM_PREVIEW_MAX_CHARS = 200;
|
|
6430
|
+
var TOOL_TRACKING_MAX_ROWS = 5;
|
|
6431
|
+
var TOOL_TRACKING_MAX_CHARS = 200;
|
|
6432
|
+
var TOOL_TRACKING_SAMPLE_ROWS = 3;
|
|
6433
|
+
var MAX_COMPONENT_QUERY_LIMIT = 10;
|
|
6434
|
+
var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
|
|
6435
|
+
var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
|
|
6436
|
+
var MAX_TOOL_CALLING_ITERATIONS = 20;
|
|
6437
|
+
var KNOWLEDGE_BASE_TOP_K = 3;
|
|
6438
|
+
|
|
6439
|
+
// src/userResponse/stream-buffer.ts
|
|
6440
|
+
var StreamBuffer = class {
|
|
6441
|
+
constructor(callback) {
|
|
6442
|
+
this.buffer = "";
|
|
6443
|
+
this.flushTimer = null;
|
|
6444
|
+
this.fullText = "";
|
|
6445
|
+
this.callback = callback;
|
|
6050
6446
|
}
|
|
6051
6447
|
/**
|
|
6052
|
-
*
|
|
6053
|
-
* @param strategy - 'best', 'fast', or 'balanced'
|
|
6448
|
+
* Check if the buffer has a callback configured
|
|
6054
6449
|
*/
|
|
6055
|
-
|
|
6056
|
-
this.
|
|
6057
|
-
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
6450
|
+
hasCallback() {
|
|
6451
|
+
return !!this.callback;
|
|
6058
6452
|
}
|
|
6059
6453
|
/**
|
|
6060
|
-
* Get
|
|
6061
|
-
* @returns The current model strategy
|
|
6454
|
+
* Get all text that has been written (including already flushed)
|
|
6062
6455
|
*/
|
|
6063
|
-
|
|
6064
|
-
return this.
|
|
6456
|
+
getFullText() {
|
|
6457
|
+
return this.fullText;
|
|
6065
6458
|
}
|
|
6066
6459
|
/**
|
|
6067
|
-
*
|
|
6068
|
-
*
|
|
6460
|
+
* Write a chunk to the buffer
|
|
6461
|
+
* Large chunks or chunks with newlines are flushed immediately
|
|
6462
|
+
* Small chunks are batched and flushed after a short interval
|
|
6463
|
+
*
|
|
6464
|
+
* @param chunk - Text chunk to write
|
|
6069
6465
|
*/
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
this.conversationSimilarityThreshold = 0.8;
|
|
6466
|
+
write(chunk) {
|
|
6467
|
+
this.fullText += chunk;
|
|
6468
|
+
if (!this.callback) {
|
|
6074
6469
|
return;
|
|
6075
6470
|
}
|
|
6076
|
-
this.
|
|
6077
|
-
|
|
6471
|
+
this.buffer += chunk;
|
|
6472
|
+
if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
|
|
6473
|
+
this.flushNow();
|
|
6474
|
+
} else if (!this.flushTimer) {
|
|
6475
|
+
this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
|
|
6476
|
+
}
|
|
6078
6477
|
}
|
|
6079
6478
|
/**
|
|
6080
|
-
*
|
|
6081
|
-
*
|
|
6479
|
+
* Flush the buffer immediately
|
|
6480
|
+
* Call this before tool execution or other operations that need clean output
|
|
6082
6481
|
*/
|
|
6083
|
-
|
|
6084
|
-
|
|
6482
|
+
flush() {
|
|
6483
|
+
this.flushNow();
|
|
6085
6484
|
}
|
|
6086
6485
|
/**
|
|
6087
|
-
*
|
|
6486
|
+
* Internal flush implementation
|
|
6088
6487
|
*/
|
|
6089
|
-
|
|
6090
|
-
|
|
6488
|
+
flushNow() {
|
|
6489
|
+
if (this.flushTimer) {
|
|
6490
|
+
clearTimeout(this.flushTimer);
|
|
6491
|
+
this.flushTimer = null;
|
|
6492
|
+
}
|
|
6493
|
+
if (this.buffer && this.callback) {
|
|
6494
|
+
this.callback(this.buffer);
|
|
6495
|
+
this.buffer = "";
|
|
6496
|
+
}
|
|
6091
6497
|
}
|
|
6092
6498
|
/**
|
|
6093
|
-
*
|
|
6094
|
-
*
|
|
6095
|
-
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
6499
|
+
* Clean up resources
|
|
6500
|
+
* Call this when done with the buffer
|
|
6096
6501
|
*/
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6502
|
+
dispose() {
|
|
6503
|
+
this.flush();
|
|
6504
|
+
this.callback = void 0;
|
|
6505
|
+
}
|
|
6506
|
+
};
|
|
6507
|
+
function streamDelay(ms = STREAM_DELAY_MS) {
|
|
6508
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6509
|
+
}
|
|
6510
|
+
async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
|
|
6511
|
+
if (!streamBuffer.hasCallback()) {
|
|
6512
|
+
return operation();
|
|
6513
|
+
}
|
|
6514
|
+
const startTime = Date.now();
|
|
6515
|
+
await streamDelay(30);
|
|
6516
|
+
streamBuffer.write(`\u23F3 ${progressMessage}`);
|
|
6517
|
+
const heartbeatInterval = setInterval(() => {
|
|
6518
|
+
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
6519
|
+
if (elapsedSeconds >= 1) {
|
|
6520
|
+
streamBuffer.write(` (${elapsedSeconds}s)`);
|
|
6521
|
+
}
|
|
6522
|
+
}, intervalMs);
|
|
6523
|
+
try {
|
|
6524
|
+
const result = await operation();
|
|
6525
|
+
return result;
|
|
6526
|
+
} finally {
|
|
6527
|
+
clearInterval(heartbeatInterval);
|
|
6528
|
+
streamBuffer.write("\n\n");
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
|
|
6532
|
+
// src/userResponse/utils/component-props-processor.ts
|
|
6533
|
+
var NUMERIC_CONFIG_KEYS = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6534
|
+
var STRING_CONFIG_KEYS = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6535
|
+
var CONFIG_FIELDS_TO_VALIDATE = [
|
|
6536
|
+
"xAxisKey",
|
|
6537
|
+
"yAxisKey",
|
|
6538
|
+
"valueKey",
|
|
6539
|
+
"nameKey",
|
|
6540
|
+
"labelKey",
|
|
6541
|
+
"groupBy",
|
|
6542
|
+
"aggregationField",
|
|
6543
|
+
"seriesKey",
|
|
6544
|
+
"sizeKey",
|
|
6545
|
+
"xAggregationField",
|
|
6546
|
+
"yAggregationField"
|
|
6547
|
+
];
|
|
6548
|
+
function findMatchingField(fieldName, configKey, validFieldNames, fieldTypes, providerName) {
|
|
6549
|
+
if (!fieldName) return null;
|
|
6550
|
+
const lowerField = fieldName.toLowerCase();
|
|
6551
|
+
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6552
|
+
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6553
|
+
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6554
|
+
const containsMatches = validFieldNames.filter(
|
|
6555
|
+
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6556
|
+
);
|
|
6557
|
+
if (containsMatches.length === 1) return containsMatches[0];
|
|
6558
|
+
if (NUMERIC_CONFIG_KEYS.includes(configKey)) {
|
|
6559
|
+
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6560
|
+
const match = numericFields.find(
|
|
6561
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6562
|
+
);
|
|
6563
|
+
if (match) return match;
|
|
6564
|
+
if (numericFields.length > 0) {
|
|
6565
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6566
|
+
return numericFields[0];
|
|
6101
6567
|
}
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6568
|
+
}
|
|
6569
|
+
if (STRING_CONFIG_KEYS.includes(configKey)) {
|
|
6570
|
+
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6571
|
+
const match = stringFields.find(
|
|
6572
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6573
|
+
);
|
|
6574
|
+
if (match) return match;
|
|
6575
|
+
if (stringFields.length > 0) {
|
|
6576
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6577
|
+
return stringFields[0];
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6581
|
+
return validFieldNames[0];
|
|
6582
|
+
}
|
|
6583
|
+
function validateConfigFieldNames(config, outputSchema, providerName) {
|
|
6584
|
+
if (!outputSchema?.fields || !config) return config;
|
|
6585
|
+
const validFieldNames = outputSchema.fields.map((f) => f.name);
|
|
6586
|
+
const fieldTypes = outputSchema.fields.reduce((acc, f) => {
|
|
6587
|
+
acc[f.name] = f.type;
|
|
6588
|
+
return acc;
|
|
6589
|
+
}, {});
|
|
6590
|
+
const correctedConfig = { ...config };
|
|
6591
|
+
for (const configKey of CONFIG_FIELDS_TO_VALIDATE) {
|
|
6592
|
+
const fieldValue = correctedConfig[configKey];
|
|
6593
|
+
if (fieldValue && typeof fieldValue === "string") {
|
|
6594
|
+
if (!validFieldNames.includes(fieldValue)) {
|
|
6595
|
+
const correctedField = findMatchingField(fieldValue, configKey, validFieldNames, fieldTypes, providerName);
|
|
6596
|
+
if (correctedField) {
|
|
6597
|
+
logger.warn(`[${providerName}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6598
|
+
correctedConfig[configKey] = correctedField;
|
|
6107
6599
|
}
|
|
6108
6600
|
}
|
|
6109
6601
|
}
|
|
6110
|
-
|
|
6602
|
+
}
|
|
6603
|
+
if (Array.isArray(correctedConfig.series)) {
|
|
6604
|
+
correctedConfig.series = correctedConfig.series.map((s) => {
|
|
6605
|
+
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6606
|
+
const correctedField = findMatchingField(s.dataKey, "yAxisKey", validFieldNames, fieldTypes, providerName);
|
|
6607
|
+
if (correctedField) {
|
|
6608
|
+
logger.warn(`[${providerName}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6609
|
+
return { ...s, dataKey: correctedField };
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
return s;
|
|
6613
|
+
});
|
|
6614
|
+
}
|
|
6615
|
+
return correctedConfig;
|
|
6616
|
+
}
|
|
6617
|
+
function validateExternalTool(externalTool, executedTools, providerName) {
|
|
6618
|
+
if (!externalTool) {
|
|
6619
|
+
return { valid: true };
|
|
6620
|
+
}
|
|
6621
|
+
const toolId = externalTool.toolId;
|
|
6622
|
+
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
6623
|
+
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
6624
|
+
if (!isValidTool) {
|
|
6625
|
+
logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
6626
|
+
return { valid: false };
|
|
6627
|
+
}
|
|
6628
|
+
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6629
|
+
return { valid: true, executedTool };
|
|
6630
|
+
}
|
|
6631
|
+
function validateAndCleanQuery(query, config) {
|
|
6632
|
+
if (!query) {
|
|
6633
|
+
return { query: null, wasModified: false };
|
|
6634
|
+
}
|
|
6635
|
+
let wasModified = false;
|
|
6636
|
+
let cleanedQuery = query;
|
|
6637
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6638
|
+
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6639
|
+
logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6640
|
+
return { query: null, wasModified: true };
|
|
6641
|
+
}
|
|
6642
|
+
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6643
|
+
if (fixed) {
|
|
6644
|
+
logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6645
|
+
wasModified = true;
|
|
6646
|
+
if (typeof cleanedQuery === "string") {
|
|
6647
|
+
cleanedQuery = fixedQuery;
|
|
6648
|
+
} else if (cleanedQuery?.sql) {
|
|
6649
|
+
cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
if (typeof cleanedQuery === "string") {
|
|
6653
|
+
const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6654
|
+
if (limitedQuery !== cleanedQuery) wasModified = true;
|
|
6655
|
+
cleanedQuery = limitedQuery;
|
|
6656
|
+
} else if (cleanedQuery?.sql) {
|
|
6657
|
+
const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6658
|
+
if (limitedSql !== cleanedQuery.sql) wasModified = true;
|
|
6659
|
+
cleanedQuery = { ...cleanedQuery, sql: limitedSql };
|
|
6660
|
+
}
|
|
6661
|
+
return { query: cleanedQuery, wasModified };
|
|
6662
|
+
}
|
|
6663
|
+
function processComponentProps(props, executedTools, config) {
|
|
6664
|
+
let cleanedProps = { ...props };
|
|
6665
|
+
if (cleanedProps.externalTool) {
|
|
6666
|
+
const { valid, executedTool } = validateExternalTool(
|
|
6667
|
+
cleanedProps.externalTool,
|
|
6668
|
+
executedTools,
|
|
6669
|
+
config.providerName
|
|
6670
|
+
);
|
|
6671
|
+
if (!valid) {
|
|
6672
|
+
cleanedProps.externalTool = null;
|
|
6673
|
+
} else if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6674
|
+
cleanedProps.config = validateConfigFieldNames(
|
|
6675
|
+
cleanedProps.config,
|
|
6676
|
+
executedTool.outputSchema,
|
|
6677
|
+
config.providerName
|
|
6678
|
+
);
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
if (cleanedProps.query) {
|
|
6682
|
+
const { query } = validateAndCleanQuery(cleanedProps.query, config);
|
|
6683
|
+
cleanedProps.query = query;
|
|
6684
|
+
}
|
|
6685
|
+
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6686
|
+
logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
6687
|
+
}
|
|
6688
|
+
return cleanedProps;
|
|
6689
|
+
}
|
|
6690
|
+
|
|
6691
|
+
// src/userResponse/services/query-execution-service.ts
|
|
6692
|
+
var QueryExecutionService = class {
|
|
6693
|
+
constructor(config) {
|
|
6694
|
+
this.config = config;
|
|
6111
6695
|
}
|
|
6112
6696
|
/**
|
|
6113
|
-
* Get the cache key for a query
|
|
6697
|
+
* Get the cache key for a query
|
|
6114
6698
|
* This ensures the cache key matches what the frontend will send
|
|
6115
|
-
* Used for both caching and internal deduplication
|
|
6116
6699
|
*/
|
|
6117
6700
|
getQueryCacheKey(query) {
|
|
6118
6701
|
if (typeof query === "string") {
|
|
@@ -6128,17 +6711,19 @@ var BaseLLM = class {
|
|
|
6128
6711
|
return "";
|
|
6129
6712
|
}
|
|
6130
6713
|
/**
|
|
6131
|
-
* Execute a query against the database
|
|
6714
|
+
* Execute a query against the database
|
|
6132
6715
|
* @param query - The SQL query to execute (string or object with sql/values)
|
|
6133
6716
|
* @param collections - Collections object containing database execute function
|
|
6134
6717
|
* @returns Object with result data and cache key
|
|
6135
|
-
* @throws Error if query execution fails
|
|
6136
6718
|
*/
|
|
6137
|
-
async
|
|
6719
|
+
async executeQuery(query, collections) {
|
|
6138
6720
|
const cacheKey = this.getQueryCacheKey(query);
|
|
6139
6721
|
if (!cacheKey) {
|
|
6140
6722
|
throw new Error("Invalid query format: expected string or object with sql property");
|
|
6141
6723
|
}
|
|
6724
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
6725
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
6726
|
+
}
|
|
6142
6727
|
const result = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6143
6728
|
return { result, cacheKey };
|
|
6144
6729
|
}
|
|
@@ -6146,7 +6731,7 @@ var BaseLLM = class {
|
|
|
6146
6731
|
* Request the LLM to fix a failed SQL query
|
|
6147
6732
|
* @param failedQuery - The query that failed execution
|
|
6148
6733
|
* @param errorMessage - The error message from the failed execution
|
|
6149
|
-
* @param componentContext - Context about the component
|
|
6734
|
+
* @param componentContext - Context about the component
|
|
6150
6735
|
* @param apiKey - Optional API key
|
|
6151
6736
|
* @returns Fixed query string
|
|
6152
6737
|
*/
|
|
@@ -6187,10 +6772,10 @@ Fixed SQL query:`;
|
|
|
6187
6772
|
user: prompt
|
|
6188
6773
|
},
|
|
6189
6774
|
{
|
|
6190
|
-
model: this.getModelForTask("simple"),
|
|
6191
|
-
maxTokens:
|
|
6775
|
+
model: this.config.getModelForTask("simple"),
|
|
6776
|
+
maxTokens: MAX_TOKENS_QUERY_FIX,
|
|
6192
6777
|
temperature: 0,
|
|
6193
|
-
apiKey: this.getApiKey(apiKey)
|
|
6778
|
+
apiKey: this.config.getApiKey(apiKey)
|
|
6194
6779
|
}
|
|
6195
6780
|
);
|
|
6196
6781
|
let fixedQuery = response.trim();
|
|
@@ -6200,23 +6785,561 @@ Fixed SQL query:`;
|
|
|
6200
6785
|
return validatedQuery;
|
|
6201
6786
|
}
|
|
6202
6787
|
/**
|
|
6203
|
-
*
|
|
6204
|
-
*
|
|
6205
|
-
*
|
|
6206
|
-
*
|
|
6207
|
-
* @
|
|
6208
|
-
* @param components - List of available components
|
|
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
|
|
6788
|
+
* Validate a single component's query with retry logic
|
|
6789
|
+
* @param component - The component to validate
|
|
6790
|
+
* @param collections - Collections object containing database execute function
|
|
6791
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6792
|
+
* @returns Validation result with component, query key, and result
|
|
6213
6793
|
*/
|
|
6214
|
-
async
|
|
6215
|
-
const
|
|
6216
|
-
const
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6794
|
+
async validateSingleQuery(component, collections, apiKey) {
|
|
6795
|
+
const query = component.props?.query;
|
|
6796
|
+
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6797
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6798
|
+
let finalQueryKey = originalQueryKey;
|
|
6799
|
+
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6800
|
+
let currentQueryStr = queryStr;
|
|
6801
|
+
let validated = false;
|
|
6802
|
+
let lastError = "";
|
|
6803
|
+
let result = null;
|
|
6804
|
+
let attempts = 0;
|
|
6805
|
+
logger.info(`[${this.config.providerName}] Validating query for component: ${component.name} (${component.type})`);
|
|
6806
|
+
while (attempts < MAX_QUERY_VALIDATION_RETRIES && !validated) {
|
|
6807
|
+
attempts++;
|
|
6808
|
+
try {
|
|
6809
|
+
logger.debug(`[${this.config.providerName}] Query validation attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES} for ${component.name}`);
|
|
6810
|
+
const validationResult = await this.executeQuery(currentQuery, collections);
|
|
6811
|
+
result = validationResult.result;
|
|
6812
|
+
validated = true;
|
|
6813
|
+
queryCache.set(validationResult.cacheKey, result);
|
|
6814
|
+
logger.info(`[${this.config.providerName}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6815
|
+
if (currentQueryStr !== queryStr) {
|
|
6816
|
+
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6817
|
+
component.props = {
|
|
6818
|
+
...component.props,
|
|
6819
|
+
query: fixedQuery
|
|
6820
|
+
};
|
|
6821
|
+
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6822
|
+
logger.info(`[${this.config.providerName}] Updated ${component.name} with fixed query`);
|
|
6823
|
+
}
|
|
6824
|
+
} catch (error) {
|
|
6825
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
6826
|
+
logger.warn(`[${this.config.providerName}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES}): ${lastError}`);
|
|
6827
|
+
if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
|
|
6828
|
+
logger.error(`[${this.config.providerName}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6829
|
+
break;
|
|
6830
|
+
}
|
|
6831
|
+
logger.info(`[${this.config.providerName}] Requesting query fix from LLM for ${component.name}...`);
|
|
6832
|
+
try {
|
|
6833
|
+
const fixedQueryStr = await this.requestQueryFix(
|
|
6834
|
+
currentQueryStr,
|
|
6835
|
+
lastError,
|
|
6836
|
+
{
|
|
6837
|
+
name: component.name,
|
|
6838
|
+
type: component.type,
|
|
6839
|
+
title: component.props?.title
|
|
6840
|
+
},
|
|
6841
|
+
apiKey
|
|
6842
|
+
);
|
|
6843
|
+
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6844
|
+
logger.info(`[${this.config.providerName}] Received fixed query for ${component.name}, retrying...`);
|
|
6845
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6846
|
+
currentQueryStr = limitedFixedQuery;
|
|
6847
|
+
if (typeof currentQuery === "string") {
|
|
6848
|
+
currentQuery = limitedFixedQuery;
|
|
6849
|
+
} else {
|
|
6850
|
+
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6851
|
+
}
|
|
6852
|
+
} else {
|
|
6853
|
+
logger.warn(`[${this.config.providerName}] LLM returned same or empty query, stopping retries`);
|
|
6854
|
+
break;
|
|
6855
|
+
}
|
|
6856
|
+
} catch (fixError) {
|
|
6857
|
+
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6858
|
+
logger.error(`[${this.config.providerName}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6859
|
+
break;
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
if (!validated) {
|
|
6864
|
+
logger.warn(`[${this.config.providerName}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6865
|
+
}
|
|
6866
|
+
return {
|
|
6867
|
+
component: validated ? component : null,
|
|
6868
|
+
queryKey: finalQueryKey,
|
|
6869
|
+
result,
|
|
6870
|
+
validated
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
/**
|
|
6874
|
+
* Validate multiple component queries in parallel
|
|
6875
|
+
* @param components - Array of components with potential queries
|
|
6876
|
+
* @param collections - Collections object containing database execute function
|
|
6877
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6878
|
+
* @returns Object with validated components and query results map
|
|
6879
|
+
*/
|
|
6880
|
+
async validateComponentQueries(components, collections, apiKey) {
|
|
6881
|
+
const queryResults = /* @__PURE__ */ new Map();
|
|
6882
|
+
const validatedComponents = [];
|
|
6883
|
+
const componentsWithoutQuery = [];
|
|
6884
|
+
const componentsWithQuery = [];
|
|
6885
|
+
for (const component of components) {
|
|
6886
|
+
if (!component.props?.query) {
|
|
6887
|
+
componentsWithoutQuery.push(component);
|
|
6888
|
+
} else {
|
|
6889
|
+
componentsWithQuery.push(component);
|
|
6890
|
+
}
|
|
6891
|
+
}
|
|
6892
|
+
validatedComponents.push(...componentsWithoutQuery);
|
|
6893
|
+
if (componentsWithQuery.length === 0) {
|
|
6894
|
+
return { components: validatedComponents, queryResults };
|
|
6895
|
+
}
|
|
6896
|
+
logger.info(`[${this.config.providerName}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6897
|
+
const validationPromises = componentsWithQuery.map(
|
|
6898
|
+
(component) => this.validateSingleQuery(component, collections, apiKey)
|
|
6899
|
+
);
|
|
6900
|
+
const results = await Promise.allSettled(validationPromises);
|
|
6901
|
+
for (let i = 0; i < results.length; i++) {
|
|
6902
|
+
const result = results[i];
|
|
6903
|
+
const component = componentsWithQuery[i];
|
|
6904
|
+
if (result.status === "fulfilled") {
|
|
6905
|
+
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6906
|
+
if (validated && validatedComponent) {
|
|
6907
|
+
validatedComponents.push(validatedComponent);
|
|
6908
|
+
if (queryResult) {
|
|
6909
|
+
queryResults.set(queryKey, queryResult);
|
|
6910
|
+
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6913
|
+
} else {
|
|
6914
|
+
logger.error(`[${this.config.providerName}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6915
|
+
}
|
|
6916
|
+
}
|
|
6917
|
+
logger.info(`[${this.config.providerName}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6918
|
+
return {
|
|
6919
|
+
components: validatedComponents,
|
|
6920
|
+
queryResults
|
|
6921
|
+
};
|
|
6922
|
+
}
|
|
6923
|
+
};
|
|
6924
|
+
|
|
6925
|
+
// src/userResponse/services/tool-executor-service.ts
|
|
6926
|
+
var ToolExecutorService = class {
|
|
6927
|
+
constructor(config) {
|
|
6928
|
+
this.queryAttempts = /* @__PURE__ */ new Map();
|
|
6929
|
+
this.toolAttempts = /* @__PURE__ */ new Map();
|
|
6930
|
+
this.executedToolsList = [];
|
|
6931
|
+
this.maxAttemptsReached = false;
|
|
6932
|
+
this.config = config;
|
|
6933
|
+
}
|
|
6934
|
+
/**
|
|
6935
|
+
* Reset state for a new execution
|
|
6936
|
+
*/
|
|
6937
|
+
reset() {
|
|
6938
|
+
this.queryAttempts.clear();
|
|
6939
|
+
this.toolAttempts.clear();
|
|
6940
|
+
this.executedToolsList = [];
|
|
6941
|
+
this.maxAttemptsReached = false;
|
|
6942
|
+
}
|
|
6943
|
+
/**
|
|
6944
|
+
* Get list of successfully executed tools
|
|
6945
|
+
*/
|
|
6946
|
+
getExecutedTools() {
|
|
6947
|
+
return this.executedToolsList;
|
|
6948
|
+
}
|
|
6949
|
+
/**
|
|
6950
|
+
* Check if max attempts were reached
|
|
6951
|
+
*/
|
|
6952
|
+
isMaxAttemptsReached() {
|
|
6953
|
+
return this.maxAttemptsReached;
|
|
6954
|
+
}
|
|
6955
|
+
/**
|
|
6956
|
+
* Create a tool handler function for LLM.streamWithTools
|
|
6957
|
+
* @param externalTools - List of available external tools
|
|
6958
|
+
* @returns Tool handler function
|
|
6959
|
+
*/
|
|
6960
|
+
createToolHandler(externalTools) {
|
|
6961
|
+
return async (toolName, toolInput) => {
|
|
6962
|
+
if (toolName === "execute_query") {
|
|
6963
|
+
return this.executeQuery(toolInput);
|
|
6964
|
+
} else {
|
|
6965
|
+
return this.executeExternalTool(toolName, toolInput, externalTools);
|
|
6966
|
+
}
|
|
6967
|
+
};
|
|
6968
|
+
}
|
|
6969
|
+
/**
|
|
6970
|
+
* Execute a SQL query with retry tracking and streaming feedback
|
|
6971
|
+
*/
|
|
6972
|
+
async executeQuery(toolInput) {
|
|
6973
|
+
let sql = toolInput.sql;
|
|
6974
|
+
const params = toolInput.params || {};
|
|
6975
|
+
const reasoning = toolInput.reasoning;
|
|
6976
|
+
const { streamBuffer, collections, providerName } = this.config;
|
|
6977
|
+
sql = ensureQueryLimit(sql, MAX_COMPONENT_QUERY_LIMIT, MAX_COMPONENT_QUERY_LIMIT);
|
|
6978
|
+
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
6979
|
+
const attempts = (this.queryAttempts.get(queryKey) || 0) + 1;
|
|
6980
|
+
this.queryAttempts.set(queryKey, attempts);
|
|
6981
|
+
if (Object.keys(params).length > 0) {
|
|
6982
|
+
logger.info(`[${providerName}] Query params: ${JSON.stringify(params)}`);
|
|
6983
|
+
}
|
|
6984
|
+
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
6985
|
+
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
6986
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
6987
|
+
this.maxAttemptsReached = true;
|
|
6988
|
+
if (streamBuffer.hasCallback()) {
|
|
6989
|
+
streamBuffer.write(`
|
|
6990
|
+
|
|
6991
|
+
\u274C ${errorMsg}
|
|
6992
|
+
|
|
6993
|
+
Please try rephrasing your question or simplifying your request.
|
|
6994
|
+
|
|
6995
|
+
`);
|
|
6996
|
+
}
|
|
6997
|
+
throw new Error(errorMsg);
|
|
6998
|
+
}
|
|
6999
|
+
try {
|
|
7000
|
+
streamBuffer.flush();
|
|
7001
|
+
if (streamBuffer.hasCallback()) {
|
|
7002
|
+
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
7003
|
+
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
7004
|
+
if (attempts === 1) {
|
|
7005
|
+
streamBuffer.write(`
|
|
7006
|
+
|
|
7007
|
+
\u{1F50D} **Analyzing your question...**
|
|
7008
|
+
|
|
7009
|
+
`);
|
|
7010
|
+
await streamDelay();
|
|
7011
|
+
if (reasoning) {
|
|
7012
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
7013
|
+
|
|
7014
|
+
`);
|
|
7015
|
+
await streamDelay();
|
|
7016
|
+
}
|
|
7017
|
+
streamBuffer.write(`\u{1F4DD} **Generated SQL Query:**
|
|
7018
|
+
\`\`\`sql
|
|
7019
|
+
${sql}
|
|
7020
|
+
\`\`\`${paramsDisplay}
|
|
7021
|
+
|
|
7022
|
+
`);
|
|
7023
|
+
await streamDelay();
|
|
7024
|
+
} else {
|
|
7025
|
+
streamBuffer.write(`
|
|
7026
|
+
|
|
7027
|
+
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
7028
|
+
|
|
7029
|
+
`);
|
|
7030
|
+
await streamDelay();
|
|
7031
|
+
if (reasoning) {
|
|
7032
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
7033
|
+
|
|
7034
|
+
`);
|
|
7035
|
+
await streamDelay();
|
|
7036
|
+
}
|
|
7037
|
+
streamBuffer.write(`\u{1F4DD} **Corrected SQL Query:**
|
|
7038
|
+
\`\`\`sql
|
|
7039
|
+
${sql}
|
|
7040
|
+
\`\`\`${paramsDisplay}
|
|
7041
|
+
|
|
7042
|
+
`);
|
|
7043
|
+
await streamDelay();
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
7047
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
7048
|
+
}
|
|
7049
|
+
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
7050
|
+
const result = await withProgressHeartbeat(
|
|
7051
|
+
() => collections["database"]["execute"](queryPayload),
|
|
7052
|
+
"Executing database query",
|
|
7053
|
+
streamBuffer
|
|
7054
|
+
);
|
|
7055
|
+
const data = result?.data || result;
|
|
7056
|
+
const rowCount = result?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7057
|
+
if (streamBuffer.hasCallback()) {
|
|
7058
|
+
streamBuffer.write(`
|
|
7059
|
+
\u2705 **Query executed successfully!**
|
|
7060
|
+
|
|
7061
|
+
`);
|
|
7062
|
+
await streamDelay();
|
|
7063
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
7064
|
+
const firstRow = data[0];
|
|
7065
|
+
const columns = Object.keys(firstRow);
|
|
7066
|
+
if (data.length === 1 && columns.length === 1) {
|
|
7067
|
+
const value = firstRow[columns[0]];
|
|
7068
|
+
streamBuffer.write(`**Result:** ${value}
|
|
7069
|
+
|
|
7070
|
+
`);
|
|
7071
|
+
await streamDelay();
|
|
7072
|
+
} else if (data.length > 0) {
|
|
7073
|
+
streamBuffer.write(`**Retrieved ${rowCount} rows**
|
|
7074
|
+
|
|
7075
|
+
`);
|
|
7076
|
+
await streamDelay();
|
|
7077
|
+
const streamPreview = formatQueryResultForLLM(data, {
|
|
7078
|
+
maxRows: STREAM_PREVIEW_MAX_ROWS,
|
|
7079
|
+
maxCharsPerField: STREAM_PREVIEW_MAX_CHARS
|
|
7080
|
+
});
|
|
7081
|
+
streamBuffer.write(`<DataTable>${JSON.stringify(streamPreview.data)}</DataTable>
|
|
7082
|
+
|
|
7083
|
+
`);
|
|
7084
|
+
if (streamPreview.truncationNote) {
|
|
7085
|
+
streamBuffer.write(`*${streamPreview.truncationNote}*
|
|
7086
|
+
|
|
7087
|
+
`);
|
|
7088
|
+
}
|
|
7089
|
+
await streamDelay();
|
|
7090
|
+
}
|
|
7091
|
+
} else if (Array.isArray(data) && data.length === 0) {
|
|
7092
|
+
streamBuffer.write(`**No rows returned.**
|
|
7093
|
+
|
|
7094
|
+
`);
|
|
7095
|
+
await streamDelay();
|
|
7096
|
+
}
|
|
7097
|
+
streamBuffer.write(`\u{1F4CA} **Analyzing results...**
|
|
7098
|
+
|
|
7099
|
+
`);
|
|
7100
|
+
}
|
|
7101
|
+
const formattedResult = formatQueryResultForLLM(data, {
|
|
7102
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7103
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7104
|
+
});
|
|
7105
|
+
if (formattedResult.truncationNote) {
|
|
7106
|
+
logger.info(`[${providerName}] Truncation: ${formattedResult.truncationNote}`);
|
|
7107
|
+
}
|
|
7108
|
+
return formatResultAsString(formattedResult);
|
|
7109
|
+
} catch (error) {
|
|
7110
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7111
|
+
logger.error(`[${providerName}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7112
|
+
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7113
|
+
if (streamBuffer.hasCallback()) {
|
|
7114
|
+
streamBuffer.write(`\u274C **Query execution failed:**
|
|
7115
|
+
\`\`\`
|
|
7116
|
+
${errorMsg}
|
|
7117
|
+
\`\`\`
|
|
7118
|
+
|
|
7119
|
+
`);
|
|
7120
|
+
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7121
|
+
streamBuffer.write(`\u{1F527} **Generating corrected query...**
|
|
7122
|
+
|
|
7123
|
+
`);
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
/**
|
|
7130
|
+
* Execute an external tool with retry tracking and streaming feedback
|
|
7131
|
+
*/
|
|
7132
|
+
async executeExternalTool(toolName, toolInput, externalTools) {
|
|
7133
|
+
const { streamBuffer, providerName } = this.config;
|
|
7134
|
+
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7135
|
+
if (!externalTool) {
|
|
7136
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
7137
|
+
}
|
|
7138
|
+
const attempts = (this.toolAttempts.get(toolName) || 0) + 1;
|
|
7139
|
+
this.toolAttempts.set(toolName, attempts);
|
|
7140
|
+
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7141
|
+
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7142
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
7143
|
+
if (streamBuffer.hasCallback()) {
|
|
7144
|
+
streamBuffer.write(`
|
|
7145
|
+
|
|
7146
|
+
\u274C ${errorMsg}
|
|
7147
|
+
|
|
7148
|
+
Please try rephrasing your request or contact support.
|
|
7149
|
+
|
|
7150
|
+
`);
|
|
7151
|
+
}
|
|
7152
|
+
throw new Error(errorMsg);
|
|
7153
|
+
}
|
|
7154
|
+
try {
|
|
7155
|
+
streamBuffer.flush();
|
|
7156
|
+
if (streamBuffer.hasCallback()) {
|
|
7157
|
+
if (attempts === 1) {
|
|
7158
|
+
streamBuffer.write(`
|
|
7159
|
+
|
|
7160
|
+
\u{1F517} **Executing ${externalTool.name}...**
|
|
7161
|
+
|
|
7162
|
+
`);
|
|
7163
|
+
} else {
|
|
7164
|
+
streamBuffer.write(`
|
|
7165
|
+
|
|
7166
|
+
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7167
|
+
|
|
7168
|
+
`);
|
|
7169
|
+
}
|
|
7170
|
+
await streamDelay();
|
|
7171
|
+
}
|
|
7172
|
+
const result = await withProgressHeartbeat(
|
|
7173
|
+
() => externalTool.fn(toolInput),
|
|
7174
|
+
`Running ${externalTool.name}`,
|
|
7175
|
+
streamBuffer
|
|
7176
|
+
);
|
|
7177
|
+
if (!this.executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7178
|
+
const formattedForTracking = formatToolResultForLLM(result, {
|
|
7179
|
+
toolName: externalTool.name,
|
|
7180
|
+
toolLimit: externalTool.limit,
|
|
7181
|
+
maxRows: TOOL_TRACKING_MAX_ROWS,
|
|
7182
|
+
maxCharsPerField: TOOL_TRACKING_MAX_CHARS
|
|
7183
|
+
});
|
|
7184
|
+
this.executedToolsList.push({
|
|
7185
|
+
id: externalTool.id,
|
|
7186
|
+
name: externalTool.name,
|
|
7187
|
+
params: toolInput,
|
|
7188
|
+
result: {
|
|
7189
|
+
_totalRecords: formattedForTracking.summary.totalRecords,
|
|
7190
|
+
_recordsShown: formattedForTracking.summary.recordsShown,
|
|
7191
|
+
_metadata: formattedForTracking.metadata,
|
|
7192
|
+
_sampleData: formattedForTracking.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS)
|
|
7193
|
+
},
|
|
7194
|
+
outputSchema: externalTool.outputSchema
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
7197
|
+
if (streamBuffer.hasCallback()) {
|
|
7198
|
+
streamBuffer.write(`\u2705 **${externalTool.name} completed successfully**
|
|
7199
|
+
|
|
7200
|
+
`);
|
|
7201
|
+
await streamDelay();
|
|
7202
|
+
}
|
|
7203
|
+
const formattedToolResult = formatToolResultForLLM(result, {
|
|
7204
|
+
toolName: externalTool.name,
|
|
7205
|
+
toolLimit: externalTool.limit,
|
|
7206
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7207
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7208
|
+
});
|
|
7209
|
+
if (formattedToolResult.truncationNote) {
|
|
7210
|
+
logger.info(`[${providerName}] Truncation: ${formattedToolResult.truncationNote}`);
|
|
7211
|
+
}
|
|
7212
|
+
return formatResultAsString(formattedToolResult);
|
|
7213
|
+
} catch (error) {
|
|
7214
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7215
|
+
logger.error(`[${providerName}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7216
|
+
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7217
|
+
if (streamBuffer.hasCallback()) {
|
|
7218
|
+
streamBuffer.write(`\u274C **${externalTool.name} failed:**
|
|
7219
|
+
\`\`\`
|
|
7220
|
+
${errorMsg}
|
|
7221
|
+
\`\`\`
|
|
7222
|
+
|
|
7223
|
+
`);
|
|
7224
|
+
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7225
|
+
streamBuffer.write(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7226
|
+
|
|
7227
|
+
`);
|
|
7228
|
+
}
|
|
7229
|
+
}
|
|
7230
|
+
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
};
|
|
7234
|
+
|
|
7235
|
+
// src/userResponse/base-llm.ts
|
|
7236
|
+
var BaseLLM = class {
|
|
7237
|
+
constructor(config) {
|
|
7238
|
+
this.model = config?.model || this.getDefaultModel();
|
|
7239
|
+
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
7240
|
+
this.defaultLimit = config?.defaultLimit || 10;
|
|
7241
|
+
this.apiKey = config?.apiKey;
|
|
7242
|
+
this.modelStrategy = config?.modelStrategy || "fast";
|
|
7243
|
+
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7244
|
+
this.queryService = new QueryExecutionService({
|
|
7245
|
+
defaultLimit: this.defaultLimit,
|
|
7246
|
+
getModelForTask: (taskType) => this.getModelForTask(taskType),
|
|
7247
|
+
getApiKey: (apiKey) => this.getApiKey(apiKey),
|
|
7248
|
+
providerName: this.getProviderName()
|
|
7249
|
+
});
|
|
7250
|
+
}
|
|
7251
|
+
/**
|
|
7252
|
+
* Get the appropriate model based on task type and model strategy
|
|
7253
|
+
* @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
|
|
7254
|
+
* @returns The model string to use for this task
|
|
7255
|
+
*/
|
|
7256
|
+
getModelForTask(taskType) {
|
|
7257
|
+
switch (this.modelStrategy) {
|
|
7258
|
+
case "best":
|
|
7259
|
+
return this.model;
|
|
7260
|
+
case "fast":
|
|
7261
|
+
return this.fastModel;
|
|
7262
|
+
case "balanced":
|
|
7263
|
+
default:
|
|
7264
|
+
return taskType === "complex" ? this.model : this.fastModel;
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
/**
|
|
7268
|
+
* Set the model strategy at runtime
|
|
7269
|
+
* @param strategy - 'best', 'fast', or 'balanced'
|
|
7270
|
+
*/
|
|
7271
|
+
setModelStrategy(strategy) {
|
|
7272
|
+
this.modelStrategy = strategy;
|
|
7273
|
+
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
7274
|
+
}
|
|
7275
|
+
/**
|
|
7276
|
+
* Get the current model strategy
|
|
7277
|
+
* @returns The current model strategy
|
|
7278
|
+
*/
|
|
7279
|
+
getModelStrategy() {
|
|
7280
|
+
return this.modelStrategy;
|
|
7281
|
+
}
|
|
7282
|
+
/**
|
|
7283
|
+
* Set the conversation similarity threshold at runtime
|
|
7284
|
+
* @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
|
|
7285
|
+
*/
|
|
7286
|
+
setConversationSimilarityThreshold(threshold) {
|
|
7287
|
+
if (threshold < 0 || threshold > 1) {
|
|
7288
|
+
logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default ${DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD}`);
|
|
7289
|
+
this.conversationSimilarityThreshold = DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7290
|
+
return;
|
|
7291
|
+
}
|
|
7292
|
+
this.conversationSimilarityThreshold = threshold;
|
|
7293
|
+
}
|
|
7294
|
+
/**
|
|
7295
|
+
* Get the current conversation similarity threshold
|
|
7296
|
+
* @returns The current threshold value
|
|
7297
|
+
*/
|
|
7298
|
+
getConversationSimilarityThreshold() {
|
|
7299
|
+
return this.conversationSimilarityThreshold;
|
|
7300
|
+
}
|
|
7301
|
+
/**
|
|
7302
|
+
* Get the API key (from instance, parameter, or environment)
|
|
7303
|
+
*/
|
|
7304
|
+
getApiKey(apiKey) {
|
|
7305
|
+
return apiKey || this.apiKey || this.getDefaultApiKey();
|
|
7306
|
+
}
|
|
7307
|
+
/**
|
|
7308
|
+
* Check if a component contains a Form (data_modification component)
|
|
7309
|
+
* Forms have hardcoded defaultValues that become stale when cached
|
|
7310
|
+
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
7311
|
+
*/
|
|
7312
|
+
containsFormComponent(component) {
|
|
7313
|
+
if (!component) return false;
|
|
7314
|
+
if (component.type === "Form" || component.name === "DynamicForm") {
|
|
7315
|
+
return true;
|
|
7316
|
+
}
|
|
7317
|
+
if (component.type === "Container" || component.name === "MultiComponentContainer") {
|
|
7318
|
+
const nestedComponents = component.props?.config?.components || [];
|
|
7319
|
+
for (const nested of nestedComponents) {
|
|
7320
|
+
if (nested.type === "Form" || nested.name === "DynamicForm") {
|
|
7321
|
+
return true;
|
|
7322
|
+
}
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
return false;
|
|
7326
|
+
}
|
|
7327
|
+
/**
|
|
7328
|
+
* Match components from text response suggestions and generate follow-up questions
|
|
7329
|
+
* Takes a text response with component suggestions (c1:type format) and matches with available components
|
|
7330
|
+
* Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
|
|
7331
|
+
* All components are placed in a default MultiComponentContainer layout
|
|
7332
|
+
* @param analysisContent - The text response containing component suggestions
|
|
7333
|
+
* @param components - List of available components
|
|
7334
|
+
* @param apiKey - Optional API key
|
|
7335
|
+
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
7336
|
+
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
7337
|
+
*/
|
|
7338
|
+
async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, componentStreamCallback, deferredTools, executedTools, collections, userId) {
|
|
7339
|
+
const methodStartTime = Date.now();
|
|
7340
|
+
const methodName = "matchComponentsFromAnalysis";
|
|
7341
|
+
logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
|
|
7342
|
+
try {
|
|
6220
7343
|
let availableComponentsText = "No components available";
|
|
6221
7344
|
if (components && components.length > 0) {
|
|
6222
7345
|
availableComponentsText = components.map((comp, idx) => {
|
|
@@ -6232,7 +7355,6 @@ Fixed SQL query:`;
|
|
|
6232
7355
|
}
|
|
6233
7356
|
let deferredToolsText = "No deferred external tools for this request.";
|
|
6234
7357
|
if (deferredTools && deferredTools.length > 0) {
|
|
6235
|
-
logger.info(`[${this.getProviderName()}] Passing ${deferredTools.length} deferred tools to component matching`);
|
|
6236
7358
|
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) => {
|
|
6237
7359
|
return `${idx + 1}. **${tool.name}**
|
|
6238
7360
|
toolId: "${tool.id}" (USE THIS EXACT VALUE - do not modify!)
|
|
@@ -6244,21 +7366,23 @@ Fixed SQL query:`;
|
|
|
6244
7366
|
}
|
|
6245
7367
|
let executedToolsText = "No external tools were executed for data fetching.";
|
|
6246
7368
|
if (executedTools && executedTools.length > 0) {
|
|
6247
|
-
|
|
6248
|
-
executedToolsText = "The following external tools were executed to fetch data.\n" + // '**IMPORTANT: For components displaying this data, use externalTool prop instead of query.**\n' +
|
|
6249
|
-
// '**IMPORTANT: Use ONLY the field names listed in outputSchema for config keys.**\n\n' +
|
|
6250
|
-
executedTools.map((tool, idx) => {
|
|
7369
|
+
executedToolsText = "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
|
|
6251
7370
|
let outputSchemaText = "Not available";
|
|
6252
7371
|
let fieldNamesList = "";
|
|
6253
|
-
|
|
7372
|
+
const recordCount = tool.result?._totalRecords ?? "unknown";
|
|
7373
|
+
let metadataText = "";
|
|
7374
|
+
if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
|
|
7375
|
+
const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
7376
|
+
metadataText = `
|
|
7377
|
+
\u{1F4CB} METADATA: ${metadataEntries}`;
|
|
7378
|
+
}
|
|
6254
7379
|
if (tool.outputSchema) {
|
|
6255
7380
|
const fields = tool.outputSchema.fields || [];
|
|
6256
|
-
recordCount = tool.result?._recordCount || (Array.isArray(tool.result) ? tool.result.length : "unknown");
|
|
6257
7381
|
const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
|
|
6258
7382
|
const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
|
|
6259
7383
|
fieldNamesList = `
|
|
6260
|
-
|
|
6261
|
-
|
|
7384
|
+
\u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
|
|
7385
|
+
\u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
|
|
6262
7386
|
const fieldsText = fields.map(
|
|
6263
7387
|
(f) => ` "${f.name}" (${f.type}): ${f.description}`
|
|
6264
7388
|
).join("\n");
|
|
@@ -6267,11 +7391,11 @@ Fixed SQL query:`;
|
|
|
6267
7391
|
${fieldsText}`;
|
|
6268
7392
|
}
|
|
6269
7393
|
return `${idx + 1}. **${tool.name}**
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
7394
|
+
toolId: "${tool.id}"
|
|
7395
|
+
toolName: "${tool.name}"
|
|
7396
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7397
|
+
recordCount: ${recordCount} rows returned${metadataText}
|
|
7398
|
+
outputSchema: ${outputSchemaText}${fieldNamesList}`;
|
|
6275
7399
|
}).join("\n\n");
|
|
6276
7400
|
}
|
|
6277
7401
|
const schemaDoc = schema.generateSchemaDocumentation();
|
|
@@ -6282,7 +7406,7 @@ ${fieldsText}`;
|
|
|
6282
7406
|
prompt: userPrompt || analysisContent,
|
|
6283
7407
|
collections,
|
|
6284
7408
|
userId,
|
|
6285
|
-
topK:
|
|
7409
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
6286
7410
|
});
|
|
6287
7411
|
knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
|
|
6288
7412
|
}
|
|
@@ -6297,30 +7421,12 @@ ${fieldsText}`;
|
|
|
6297
7421
|
KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext,
|
|
6298
7422
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6299
7423
|
});
|
|
6300
|
-
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
7424
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "system", extractPromptText(prompts.system));
|
|
6318
7425
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "user", `Text Analysis:
|
|
6319
7426
|
${analysisContent}
|
|
6320
7427
|
|
|
6321
7428
|
Executed Tools:
|
|
6322
7429
|
${executedToolsText}`);
|
|
6323
|
-
logCollector?.info("Matching components from text response...");
|
|
6324
7430
|
let fullResponseText = "";
|
|
6325
7431
|
let answerComponentExtracted = false;
|
|
6326
7432
|
const answerCallback = componentStreamCallback;
|
|
@@ -6380,52 +7486,44 @@ ${executedToolsText}`);
|
|
|
6380
7486
|
...answerComponentData.props
|
|
6381
7487
|
}
|
|
6382
7488
|
};
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
7489
|
+
let answerQuery = answerComponent.props?.query;
|
|
7490
|
+
if (answerQuery) {
|
|
7491
|
+
if (typeof answerQuery === "string") {
|
|
7492
|
+
answerQuery = ensureQueryLimit(answerQuery, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7493
|
+
} else if (answerQuery?.sql) {
|
|
7494
|
+
const queryObj = answerQuery;
|
|
7495
|
+
answerQuery = { ...queryObj, sql: ensureQueryLimit(queryObj.sql, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT) };
|
|
7496
|
+
}
|
|
7497
|
+
answerComponent.props.query = answerQuery;
|
|
6392
7498
|
}
|
|
6393
|
-
const answerQuery = answerComponent.props?.query;
|
|
6394
|
-
logger.info(`[${this.getProviderName()}] Answer component detected: ${answerComponent.name} (${answerComponent.type}), hasQuery: ${!!answerQuery}, hasDbExecute: ${!!collections?.["database"]?.["execute"]}`);
|
|
6395
7499
|
if (answerQuery && collections?.["database"]?.["execute"]) {
|
|
6396
7500
|
(async () => {
|
|
6397
|
-
const
|
|
7501
|
+
const maxRetries = MAX_QUERY_VALIDATION_RETRIES;
|
|
6398
7502
|
let attempts = 0;
|
|
6399
7503
|
let validated = false;
|
|
6400
7504
|
let currentQuery = answerQuery;
|
|
6401
7505
|
let currentQueryStr = typeof answerQuery === "string" ? answerQuery : answerQuery?.sql || "";
|
|
6402
7506
|
let lastError = "";
|
|
6403
|
-
|
|
6404
|
-
while (attempts < MAX_RETRIES && !validated) {
|
|
7507
|
+
while (attempts < maxRetries && !validated) {
|
|
6405
7508
|
attempts++;
|
|
6406
7509
|
try {
|
|
6407
|
-
const cacheKey = this.getQueryCacheKey(currentQuery);
|
|
7510
|
+
const cacheKey = this.queryService.getQueryCacheKey(currentQuery);
|
|
6408
7511
|
if (cacheKey) {
|
|
6409
|
-
logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${MAX_RETRIES}`);
|
|
6410
7512
|
const result2 = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6411
7513
|
queryCache.set(cacheKey, result2);
|
|
6412
7514
|
validated = true;
|
|
6413
7515
|
if (currentQuery !== answerQuery) {
|
|
6414
7516
|
answerComponent.props.query = currentQuery;
|
|
6415
7517
|
}
|
|
6416
|
-
logger.info(`[${this.getProviderName()}] \u2713 Answer component query validated (attempt ${attempts}) - STREAMING TO FRONTEND NOW`);
|
|
6417
|
-
logCollector?.info(`\u2713 Answer component query validated - streaming to frontend`);
|
|
6418
|
-
logger.info(`[${this.getProviderName()}] Calling answerCallback for: ${answerComponent.name}`);
|
|
6419
7518
|
answerCallback(answerComponent);
|
|
6420
|
-
logger.info(`[${this.getProviderName()}] answerCallback completed for: ${answerComponent.name}`);
|
|
6421
7519
|
}
|
|
6422
7520
|
} catch (validationError) {
|
|
6423
7521
|
lastError = validationError instanceof Error ? validationError.message : String(validationError);
|
|
6424
|
-
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${
|
|
6425
|
-
if (attempts <
|
|
7522
|
+
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${maxRetries}): ${lastError}`);
|
|
7523
|
+
if (attempts < maxRetries) {
|
|
6426
7524
|
try {
|
|
6427
7525
|
logger.info(`[${this.getProviderName()}] Requesting LLM to fix answer component query...`);
|
|
6428
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
7526
|
+
const fixedQueryStr = await this.queryService.requestQueryFix(
|
|
6429
7527
|
currentQueryStr,
|
|
6430
7528
|
lastError,
|
|
6431
7529
|
{
|
|
@@ -6435,7 +7533,7 @@ ${executedToolsText}`);
|
|
|
6435
7533
|
},
|
|
6436
7534
|
apiKey
|
|
6437
7535
|
);
|
|
6438
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit,
|
|
7536
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6439
7537
|
if (typeof currentQuery === "string") {
|
|
6440
7538
|
currentQuery = limitedFixedQuery;
|
|
6441
7539
|
} else {
|
|
@@ -6453,7 +7551,6 @@ ${executedToolsText}`);
|
|
|
6453
7551
|
}
|
|
6454
7552
|
if (!validated) {
|
|
6455
7553
|
logger.warn(`[${this.getProviderName()}] Answer component query validation failed after ${attempts} attempts - component will be excluded`);
|
|
6456
|
-
logCollector?.warn(`Answer component query validation failed: ${lastError} - component will be excluded from response`);
|
|
6457
7554
|
}
|
|
6458
7555
|
})();
|
|
6459
7556
|
} else {
|
|
@@ -6464,7 +7561,7 @@ ${executedToolsText}`);
|
|
|
6464
7561
|
}
|
|
6465
7562
|
}
|
|
6466
7563
|
} catch (e) {
|
|
6467
|
-
logger.
|
|
7564
|
+
logger.error(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
|
|
6468
7565
|
}
|
|
6469
7566
|
}
|
|
6470
7567
|
}
|
|
@@ -6476,7 +7573,7 @@ ${executedToolsText}`);
|
|
|
6476
7573
|
},
|
|
6477
7574
|
{
|
|
6478
7575
|
model: this.getModelForTask("complex"),
|
|
6479
|
-
maxTokens:
|
|
7576
|
+
maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
|
|
6480
7577
|
temperature: 0,
|
|
6481
7578
|
apiKey: this.getApiKey(apiKey),
|
|
6482
7579
|
partial: partialCallback
|
|
@@ -6494,155 +7591,20 @@ ${executedToolsText}`);
|
|
|
6494
7591
|
logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
|
|
6495
7592
|
const rawActions = result.actions || [];
|
|
6496
7593
|
const actions = convertQuestionsToActions(rawActions);
|
|
6497
|
-
if (matchedComponents.length > 0) {
|
|
6498
|
-
matchedComponents.forEach((comp, idx) => {
|
|
6499
|
-
logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
|
|
6500
|
-
if (comp.props?.query) {
|
|
6501
|
-
logCollector?.logQuery(
|
|
6502
|
-
`Component ${idx + 1} query`,
|
|
6503
|
-
comp.props.query,
|
|
6504
|
-
{ componentName: comp.componentName, title: comp.props.title }
|
|
6505
|
-
);
|
|
6506
|
-
}
|
|
6507
|
-
});
|
|
6508
|
-
}
|
|
6509
7594
|
const finalComponents = matchedComponents.map((mc) => {
|
|
6510
7595
|
const originalComponent = components.find((c) => c.id === mc.componentId);
|
|
6511
7596
|
if (!originalComponent) {
|
|
6512
|
-
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
6513
|
-
return null;
|
|
6514
|
-
}
|
|
6515
|
-
let cleanedProps = { ...mc.props };
|
|
6516
|
-
if (cleanedProps.externalTool) {
|
|
6517
|
-
const toolId = cleanedProps.externalTool.toolId;
|
|
6518
|
-
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
6519
|
-
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
6520
|
-
if (!isValidTool) {
|
|
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
|
-
};
|
|
6641
|
-
}
|
|
6642
|
-
}
|
|
6643
|
-
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6644
|
-
logger.info(`[${this.getProviderName()}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
7597
|
+
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
7598
|
+
return null;
|
|
6645
7599
|
}
|
|
7600
|
+
const cleanedProps = processComponentProps(
|
|
7601
|
+
mc.props,
|
|
7602
|
+
executedTools,
|
|
7603
|
+
{
|
|
7604
|
+
providerName: this.getProviderName(),
|
|
7605
|
+
defaultLimit: this.defaultLimit
|
|
7606
|
+
}
|
|
7607
|
+
);
|
|
6646
7608
|
return {
|
|
6647
7609
|
...originalComponent,
|
|
6648
7610
|
props: {
|
|
@@ -6653,27 +7615,22 @@ ${executedToolsText}`);
|
|
|
6653
7615
|
}).filter(Boolean);
|
|
6654
7616
|
let validatedComponents = finalComponents;
|
|
6655
7617
|
if (collections?.["database"]?.["execute"]) {
|
|
6656
|
-
logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
|
|
6657
|
-
logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
|
|
6658
7618
|
try {
|
|
6659
|
-
const validationResult = await this.
|
|
7619
|
+
const validationResult = await this.queryService.validateComponentQueries(
|
|
6660
7620
|
finalComponents,
|
|
6661
7621
|
collections,
|
|
6662
|
-
apiKey
|
|
6663
|
-
logCollector
|
|
7622
|
+
apiKey
|
|
6664
7623
|
);
|
|
6665
7624
|
validatedComponents = validationResult.components;
|
|
6666
7625
|
const queriedComponents = finalComponents.filter((c) => c.props?.query);
|
|
6667
7626
|
const validatedQueries = validatedComponents.filter((c) => c.props?.query);
|
|
6668
7627
|
logger.info(`[${this.getProviderName()}] Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
|
|
6669
|
-
logCollector?.info(`Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
|
|
6670
7628
|
} catch (validationError) {
|
|
6671
7629
|
const validationErrorMsg = validationError instanceof Error ? validationError.message : String(validationError);
|
|
6672
7630
|
logger.error(`[${this.getProviderName()}] Query validation error: ${validationErrorMsg}`);
|
|
6673
|
-
logCollector?.error(`Query validation error: ${validationErrorMsg}`);
|
|
6674
7631
|
}
|
|
6675
7632
|
} else {
|
|
6676
|
-
logger.
|
|
7633
|
+
logger.error(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
|
|
6677
7634
|
}
|
|
6678
7635
|
const methodDuration = Date.now() - methodStartTime;
|
|
6679
7636
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${validatedComponents.length} | actions: ${actions.length}`);
|
|
@@ -6687,7 +7644,6 @@ ${executedToolsText}`);
|
|
|
6687
7644
|
const methodDuration = Date.now() - methodStartTime;
|
|
6688
7645
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6689
7646
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
6690
|
-
logCollector?.error(`Failed to match components: ${errorMsg}`);
|
|
6691
7647
|
return {
|
|
6692
7648
|
components: [],
|
|
6693
7649
|
layoutTitle: "Dashboard",
|
|
@@ -6696,158 +7652,11 @@ ${executedToolsText}`);
|
|
|
6696
7652
|
};
|
|
6697
7653
|
}
|
|
6698
7654
|
}
|
|
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
7655
|
/**
|
|
6847
7656
|
* Classify user question into category and detect external tools needed
|
|
6848
7657
|
* Determines if question is for data analysis, requires external tools, or needs text response
|
|
6849
7658
|
*/
|
|
6850
|
-
async classifyQuestionCategory(userPrompt, apiKey,
|
|
7659
|
+
async classifyQuestionCategory(userPrompt, apiKey, conversationHistory, externalTools) {
|
|
6851
7660
|
const methodStartTime = Date.now();
|
|
6852
7661
|
const methodName = "classifyQuestionCategory";
|
|
6853
7662
|
const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
|
|
@@ -6867,24 +7676,8 @@ ${executedToolsText}`);
|
|
|
6867
7676
|
SCHEMA_DOC: schemaDoc || "No database schema available",
|
|
6868
7677
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6869
7678
|
});
|
|
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));
|
|
7679
|
+
logger.logLLMPrompt("classifyQuestionCategory", "system", extractPromptText(prompts.system));
|
|
7680
|
+
logger.logLLMPrompt("classifyQuestionCategory", "user", extractPromptText(prompts.user));
|
|
6888
7681
|
const result = await LLM.stream(
|
|
6889
7682
|
{
|
|
6890
7683
|
sys: prompts.system,
|
|
@@ -6892,23 +7685,13 @@ ${executedToolsText}`);
|
|
|
6892
7685
|
},
|
|
6893
7686
|
{
|
|
6894
7687
|
model: this.getModelForTask("simple"),
|
|
6895
|
-
maxTokens:
|
|
7688
|
+
maxTokens: MAX_TOKENS_CLASSIFICATION,
|
|
6896
7689
|
temperature: 0,
|
|
6897
7690
|
apiKey: this.getApiKey(apiKey)
|
|
6898
7691
|
},
|
|
6899
7692
|
true
|
|
6900
7693
|
// Parse as JSON
|
|
6901
7694
|
);
|
|
6902
|
-
logCollector?.logExplanation(
|
|
6903
|
-
"Question category classified",
|
|
6904
|
-
result.reasoning || "No reasoning provided",
|
|
6905
|
-
{
|
|
6906
|
-
category: result.category,
|
|
6907
|
-
externalTools: result.externalTools || [],
|
|
6908
|
-
dataAnalysisType: result.dataAnalysisType,
|
|
6909
|
-
confidence: result.confidence
|
|
6910
|
-
}
|
|
6911
|
-
);
|
|
6912
7695
|
const methodDuration = Date.now() - methodStartTime;
|
|
6913
7696
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | category: ${result.category} | confidence: ${result.confidence}% | tools: ${(result.externalTools || []).length}`);
|
|
6914
7697
|
return {
|
|
@@ -6922,7 +7705,6 @@ ${executedToolsText}`);
|
|
|
6922
7705
|
const methodDuration = Date.now() - methodStartTime;
|
|
6923
7706
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6924
7707
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
6925
|
-
logger.debug(`[${this.getProviderName()}] Category classification error details:`, error);
|
|
6926
7708
|
throw error;
|
|
6927
7709
|
}
|
|
6928
7710
|
}
|
|
@@ -6931,7 +7713,7 @@ ${executedToolsText}`);
|
|
|
6931
7713
|
* Takes a matched UI block from semantic search and modifies its props to answer the new question
|
|
6932
7714
|
* Also adapts the cached text response to match the new question
|
|
6933
7715
|
*/
|
|
6934
|
-
async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey,
|
|
7716
|
+
async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, cachedTextResponse) {
|
|
6935
7717
|
const methodStartTime = Date.now();
|
|
6936
7718
|
const methodName = "adaptUIBlockParameters";
|
|
6937
7719
|
const promptPreview = currentUserPrompt.substring(0, 50) + (currentUserPrompt.length > 50 ? "..." : "");
|
|
@@ -6965,7 +7747,7 @@ ${executedToolsText}`);
|
|
|
6965
7747
|
},
|
|
6966
7748
|
{
|
|
6967
7749
|
model: this.getModelForTask("complex"),
|
|
6968
|
-
maxTokens:
|
|
7750
|
+
maxTokens: MAX_TOKENS_ADAPTATION,
|
|
6969
7751
|
temperature: 0,
|
|
6970
7752
|
apiKey: this.getApiKey(apiKey)
|
|
6971
7753
|
},
|
|
@@ -6975,11 +7757,6 @@ ${executedToolsText}`);
|
|
|
6975
7757
|
if (!result.success) {
|
|
6976
7758
|
const methodDuration2 = Date.now() - methodStartTime;
|
|
6977
7759
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: adaptation failed - ${result.reason}`);
|
|
6978
|
-
logCollector?.warn(
|
|
6979
|
-
"Could not adapt matched UI block",
|
|
6980
|
-
"explanation",
|
|
6981
|
-
{ reason: result.reason }
|
|
6982
|
-
);
|
|
6983
7760
|
return {
|
|
6984
7761
|
success: false,
|
|
6985
7762
|
explanation: result.explanation || "Adaptation not possible"
|
|
@@ -6991,14 +7768,6 @@ ${executedToolsText}`);
|
|
|
6991
7768
|
this.defaultLimit
|
|
6992
7769
|
);
|
|
6993
7770
|
}
|
|
6994
|
-
logCollector?.logExplanation(
|
|
6995
|
-
"UI block parameters adapted",
|
|
6996
|
-
result.explanation || "Parameters adapted successfully",
|
|
6997
|
-
{
|
|
6998
|
-
parametersChanged: result.parametersChanged || [],
|
|
6999
|
-
componentType: result.adaptedComponent?.type
|
|
7000
|
-
}
|
|
7001
|
-
);
|
|
7002
7771
|
const methodDuration = Date.now() - methodStartTime;
|
|
7003
7772
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | result: success | changes: ${(result.parametersChanged || []).length}`);
|
|
7004
7773
|
return {
|
|
@@ -7012,7 +7781,6 @@ ${executedToolsText}`);
|
|
|
7012
7781
|
const methodDuration = Date.now() - methodStartTime;
|
|
7013
7782
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7014
7783
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
7015
|
-
logger.debug(`[${this.getProviderName()}] Adaptation error details:`, error);
|
|
7016
7784
|
return {
|
|
7017
7785
|
success: false,
|
|
7018
7786
|
explanation: `Error adapting parameters: ${errorMsg}`
|
|
@@ -7025,14 +7793,12 @@ ${executedToolsText}`);
|
|
|
7025
7793
|
* Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
|
|
7026
7794
|
* After generating text response, if components are provided, matches suggested components
|
|
7027
7795
|
*/
|
|
7028
|
-
async generateTextResponse(userPrompt, apiKey,
|
|
7796
|
+
async generateTextResponse(userPrompt, apiKey, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
|
|
7029
7797
|
const methodStartTime = Date.now();
|
|
7030
7798
|
const methodName = "generateTextResponse";
|
|
7031
7799
|
const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
|
|
7032
7800
|
logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")} | category: ${category} | prompt: "${promptPreview}"`);
|
|
7033
7801
|
const errors = [];
|
|
7034
|
-
logger.debug(`[${this.getProviderName()}] Starting text response generation`);
|
|
7035
|
-
logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
|
|
7036
7802
|
try {
|
|
7037
7803
|
let availableToolsDoc = "No external tools are available for this request.";
|
|
7038
7804
|
if (externalTools && externalTools.length > 0) {
|
|
@@ -7082,7 +7848,7 @@ ${executedToolsText}`);
|
|
|
7082
7848
|
prompt: userPrompt,
|
|
7083
7849
|
collections,
|
|
7084
7850
|
userId,
|
|
7085
|
-
topK:
|
|
7851
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
7086
7852
|
});
|
|
7087
7853
|
const knowledgeBaseContext = kbResult.combinedContext;
|
|
7088
7854
|
const prompts = await promptLoader.loadPrompts("text-response", {
|
|
@@ -7094,27 +7860,8 @@ ${executedToolsText}`);
|
|
|
7094
7860
|
AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc,
|
|
7095
7861
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
7096
7862
|
});
|
|
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));
|
|
7115
|
-
logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
|
|
7116
|
-
logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
|
|
7117
|
-
logCollector?.info("Generating text response with query execution capability...");
|
|
7863
|
+
logger.logLLMPrompt("generateTextResponse", "system", extractPromptText(prompts.system));
|
|
7864
|
+
logger.logLLMPrompt("generateTextResponse", "user", extractPromptText(prompts.user));
|
|
7118
7865
|
const tools = [{
|
|
7119
7866
|
name: "execute_query",
|
|
7120
7867
|
description: "Executes a parameterized SQL query against the database. CRITICAL: NEVER hardcode literal values in WHERE/HAVING conditions - ALWAYS use $paramName placeholders and pass actual values in params object.",
|
|
@@ -7143,7 +7890,6 @@ ${executedToolsText}`);
|
|
|
7143
7890
|
const executableTools = externalTools.filter(
|
|
7144
7891
|
(t) => t.executionType === "immediate" || t.executionType === "deferred" && t.userProvidedData
|
|
7145
7892
|
);
|
|
7146
|
-
logger.info(`[${this.getProviderName()}] Executable tools: ${executableTools.length} of ${externalTools.length} total`);
|
|
7147
7893
|
const addedToolIds = /* @__PURE__ */ new Set();
|
|
7148
7894
|
executableTools.forEach((tool) => {
|
|
7149
7895
|
if (addedToolIds.has(tool.id)) {
|
|
@@ -7151,7 +7897,6 @@ ${executedToolsText}`);
|
|
|
7151
7897
|
return;
|
|
7152
7898
|
}
|
|
7153
7899
|
addedToolIds.add(tool.id);
|
|
7154
|
-
logger.info(`[${this.getProviderName()}] Processing executable tool:`, JSON.stringify(tool, null, 2));
|
|
7155
7900
|
const properties = {};
|
|
7156
7901
|
const required = [];
|
|
7157
7902
|
Object.entries(tool.params || {}).forEach(([key, typeOrValue]) => {
|
|
@@ -7220,327 +7965,26 @@ ${executedToolsText}`);
|
|
|
7220
7965
|
input_schema: inputSchema
|
|
7221
7966
|
});
|
|
7222
7967
|
});
|
|
7223
|
-
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
|
-
logger.
|
|
7225
|
-
}
|
|
7226
|
-
const
|
|
7227
|
-
const
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
const wrappedStreamCallback = streamCallback ? (chunk) => {
|
|
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
|
-
};
|
|
7968
|
+
logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
|
|
7969
|
+
logger.debug(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
|
|
7970
|
+
}
|
|
7971
|
+
const streamBuffer = new StreamBuffer(streamCallback);
|
|
7972
|
+
const toolExecutor = new ToolExecutorService({
|
|
7973
|
+
providerName: this.getProviderName(),
|
|
7974
|
+
collections,
|
|
7975
|
+
streamBuffer
|
|
7976
|
+
});
|
|
7977
|
+
const executableExternalTools = externalTools?.map((t) => ({
|
|
7978
|
+
id: t.id,
|
|
7979
|
+
name: t.name,
|
|
7980
|
+
description: t.description,
|
|
7981
|
+
fn: t.fn,
|
|
7982
|
+
limit: t.limit,
|
|
7983
|
+
outputSchema: t.outputSchema,
|
|
7984
|
+
executionType: t.executionType,
|
|
7985
|
+
userProvidedData: t.userProvidedData
|
|
7986
|
+
})) || [];
|
|
7987
|
+
const toolHandler = toolExecutor.createToolHandler(executableExternalTools);
|
|
7544
7988
|
const result = await LLM.streamWithTools(
|
|
7545
7989
|
{
|
|
7546
7990
|
sys: prompts.system,
|
|
@@ -7550,21 +7994,17 @@ ${errorMsg}
|
|
|
7550
7994
|
toolHandler,
|
|
7551
7995
|
{
|
|
7552
7996
|
model: this.getModelForTask("complex"),
|
|
7553
|
-
maxTokens:
|
|
7997
|
+
maxTokens: MAX_TOKENS_TEXT_RESPONSE,
|
|
7554
7998
|
temperature: 0,
|
|
7555
7999
|
apiKey: this.getApiKey(apiKey),
|
|
7556
|
-
partial:
|
|
7557
|
-
// Pass the wrapped streaming callback to LLM
|
|
8000
|
+
partial: streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
|
|
7558
8001
|
},
|
|
7559
|
-
|
|
7560
|
-
// max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
|
|
8002
|
+
MAX_TOOL_CALLING_ITERATIONS
|
|
7561
8003
|
);
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
if (maxAttemptsReached) {
|
|
8004
|
+
const textResponse = streamBuffer.getFullText() || result || "I apologize, but I was unable to generate a response.";
|
|
8005
|
+
if (toolExecutor.isMaxAttemptsReached()) {
|
|
7565
8006
|
const methodDuration2 = Date.now() - methodStartTime;
|
|
7566
8007
|
logger.warn(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: max attempts reached`);
|
|
7567
|
-
logCollector?.error("Failed to generate valid query after maximum attempts");
|
|
7568
8008
|
return {
|
|
7569
8009
|
success: false,
|
|
7570
8010
|
errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
|
|
@@ -7576,18 +8016,10 @@ ${errorMsg}
|
|
|
7576
8016
|
}
|
|
7577
8017
|
};
|
|
7578
8018
|
}
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
"
|
|
7582
|
-
"
|
|
7583
|
-
{
|
|
7584
|
-
textLength: textResponse.length
|
|
7585
|
-
}
|
|
7586
|
-
);
|
|
7587
|
-
flushStream();
|
|
7588
|
-
if (wrappedStreamCallback && components && components.length > 0 && category !== "general") {
|
|
7589
|
-
wrappedStreamCallback("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
|
|
7590
|
-
wrappedStreamCallback("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
|
|
8019
|
+
streamBuffer.flush();
|
|
8020
|
+
if (streamBuffer.hasCallback() && components && components.length > 0 && category !== "general") {
|
|
8021
|
+
streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
|
|
8022
|
+
streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
|
|
7591
8023
|
}
|
|
7592
8024
|
let matchedComponents = [];
|
|
7593
8025
|
let layoutTitle = "Dashboard";
|
|
@@ -7595,8 +8027,6 @@ ${errorMsg}
|
|
|
7595
8027
|
let actions = [];
|
|
7596
8028
|
if (category === "general") {
|
|
7597
8029
|
logger.info(`[${this.getProviderName()}] Skipping component generation for general/conversational question`);
|
|
7598
|
-
logCollector?.info("Skipping component generation for general question");
|
|
7599
|
-
logger.info(`[${this.getProviderName()}] Generating actions for general question...`);
|
|
7600
8030
|
const nextQuestions = await this.generateNextQuestions(
|
|
7601
8031
|
userPrompt,
|
|
7602
8032
|
null,
|
|
@@ -7604,23 +8034,16 @@ ${errorMsg}
|
|
|
7604
8034
|
void 0,
|
|
7605
8035
|
// no component data
|
|
7606
8036
|
apiKey,
|
|
7607
|
-
logCollector,
|
|
7608
8037
|
conversationHistory,
|
|
7609
8038
|
textResponse
|
|
7610
8039
|
// pass text response as context
|
|
7611
8040
|
);
|
|
7612
8041
|
actions = convertQuestionsToActions(nextQuestions);
|
|
7613
|
-
logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions for general question`);
|
|
7614
8042
|
} else if (components && components.length > 0) {
|
|
7615
|
-
|
|
7616
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback setup: wrappedStreamCallback=${!!wrappedStreamCallback}, category=${category}`);
|
|
7617
|
-
const componentStreamCallback = wrappedStreamCallback && category !== "data_modification" ? (component) => {
|
|
7618
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback INVOKED for: ${component.name} (${component.type})`);
|
|
8043
|
+
const componentStreamCallback = streamBuffer.hasCallback() && category === "data_analysis" ? (component) => {
|
|
7619
8044
|
const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
|
|
7620
|
-
|
|
7621
|
-
logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
|
|
8045
|
+
streamBuffer.write(answerMarker);
|
|
7622
8046
|
} : void 0;
|
|
7623
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback created: ${!!componentStreamCallback}`);
|
|
7624
8047
|
const deferredTools = externalTools?.filter((t) => {
|
|
7625
8048
|
if (t.executionType === "deferred" && !t.userProvidedData) return true;
|
|
7626
8049
|
if (category === "data_modification" && !t.userProvidedData) {
|
|
@@ -7641,10 +8064,9 @@ ${errorMsg}
|
|
|
7641
8064
|
components,
|
|
7642
8065
|
userPrompt,
|
|
7643
8066
|
apiKey,
|
|
7644
|
-
logCollector,
|
|
7645
8067
|
componentStreamCallback,
|
|
7646
8068
|
deferredTools,
|
|
7647
|
-
|
|
8069
|
+
toolExecutor.getExecutedTools(),
|
|
7648
8070
|
collections,
|
|
7649
8071
|
userId
|
|
7650
8072
|
);
|
|
@@ -7655,8 +8077,6 @@ ${errorMsg}
|
|
|
7655
8077
|
}
|
|
7656
8078
|
let container_componet = null;
|
|
7657
8079
|
if (matchedComponents.length > 0) {
|
|
7658
|
-
logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
7659
|
-
logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
7660
8080
|
container_componet = {
|
|
7661
8081
|
id: `container_${Date.now()}`,
|
|
7662
8082
|
name: "MultiComponentContainer",
|
|
@@ -7688,7 +8108,6 @@ ${errorMsg}
|
|
|
7688
8108
|
const methodDuration = Date.now() - methodStartTime;
|
|
7689
8109
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7690
8110
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
7691
|
-
logCollector?.error(`Error generating text response: ${errorMsg}`);
|
|
7692
8111
|
userPromptErrorLogger.logLlmError(
|
|
7693
8112
|
this.getProviderName(),
|
|
7694
8113
|
this.model,
|
|
@@ -7716,38 +8135,29 @@ ${errorMsg}
|
|
|
7716
8135
|
* 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
|
|
7717
8136
|
* 3. Route appropriately based on category and response mode
|
|
7718
8137
|
*/
|
|
7719
|
-
async handleUserRequest(userPrompt, components, apiKey,
|
|
8138
|
+
async handleUserRequest(userPrompt, components, apiKey, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
|
|
7720
8139
|
const startTime = Date.now();
|
|
7721
|
-
logger.info(`[${this.getProviderName()}] handleUserRequest called for user prompt: ${userPrompt}`);
|
|
7722
|
-
logCollector?.info(`Starting request processing with mode: ${responseMode}`);
|
|
7723
8140
|
logger.clearFile();
|
|
7724
8141
|
logger.logLLMPrompt("handleUserRequest", "user", `User Prompt: ${userPrompt}`);
|
|
7725
8142
|
try {
|
|
7726
|
-
logger.info(`[${this.getProviderName()}] Step 1: Searching previous conversations...`);
|
|
7727
8143
|
const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
|
|
7728
8144
|
userPrompt,
|
|
7729
8145
|
collections,
|
|
7730
8146
|
userId,
|
|
7731
|
-
similarityThreshold:
|
|
8147
|
+
similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
|
|
7732
8148
|
});
|
|
7733
8149
|
if (conversationMatch) {
|
|
7734
8150
|
logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
|
|
7735
|
-
logCollector?.info(
|
|
7736
|
-
`\u2713 Found similar conversation (${(conversationMatch.similarity * 100).toFixed(2)}% match)`
|
|
7737
|
-
);
|
|
7738
8151
|
const rawComponent = conversationMatch.uiBlock?.component || conversationMatch.uiBlock?.generatedComponentMetadata;
|
|
7739
8152
|
const isValidComponent = rawComponent && typeof rawComponent === "object" && Object.keys(rawComponent).length > 0;
|
|
7740
8153
|
const component = isValidComponent ? rawComponent : null;
|
|
7741
8154
|
const cachedTextResponse = conversationMatch.uiBlock?.analysis || conversationMatch.uiBlock?.textResponse || conversationMatch.uiBlock?.text || "";
|
|
7742
8155
|
if (this.containsFormComponent(component)) {
|
|
7743
8156
|
logger.info(`[${this.getProviderName()}] Skipping cached result - Form components contain stale defaultValues, fetching fresh data`);
|
|
7744
|
-
logCollector?.info("Skipping cache for form - fetching current values from database...");
|
|
7745
8157
|
} else if (!component) {
|
|
7746
|
-
if (conversationMatch.similarity >=
|
|
8158
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7747
8159
|
const elapsedTime2 = Date.now() - startTime;
|
|
7748
|
-
logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response`);
|
|
7749
|
-
logCollector?.info(`\u2713 Exact match for general question - returning cached response`);
|
|
7750
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
8160
|
+
logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response (${elapsedTime2}ms)`);
|
|
7751
8161
|
return {
|
|
7752
8162
|
success: true,
|
|
7753
8163
|
data: {
|
|
@@ -7762,14 +8172,11 @@ ${errorMsg}
|
|
|
7762
8172
|
};
|
|
7763
8173
|
} else {
|
|
7764
8174
|
logger.info(`[${this.getProviderName()}] Similar match but no component (general question) - processing fresh`);
|
|
7765
|
-
logCollector?.info("Similar match found but was a general conversation - processing as new question");
|
|
7766
8175
|
}
|
|
7767
8176
|
} else {
|
|
7768
|
-
if (conversationMatch.similarity >=
|
|
8177
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7769
8178
|
const elapsedTime2 = Date.now() - startTime;
|
|
7770
|
-
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
|
|
7771
|
-
logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
|
|
7772
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
8179
|
+
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation (${elapsedTime2}ms)`);
|
|
7773
8180
|
if (streamCallback && cachedTextResponse) {
|
|
7774
8181
|
logger.info(`[${this.getProviderName()}] Streaming cached text response to frontend`);
|
|
7775
8182
|
streamCallback(cachedTextResponse);
|
|
@@ -7788,22 +8195,18 @@ ${errorMsg}
|
|
|
7788
8195
|
errors: []
|
|
7789
8196
|
};
|
|
7790
8197
|
}
|
|
7791
|
-
|
|
8198
|
+
logger.info(`[${this.getProviderName()}] Adapting parameters for similar question...`);
|
|
7792
8199
|
const originalPrompt = conversationMatch.metadata?.userPrompt || "Previous question";
|
|
7793
8200
|
const adaptResult = await this.adaptUIBlockParameters(
|
|
7794
8201
|
userPrompt,
|
|
7795
8202
|
originalPrompt,
|
|
7796
8203
|
conversationMatch.uiBlock,
|
|
7797
8204
|
apiKey,
|
|
7798
|
-
logCollector,
|
|
7799
8205
|
cachedTextResponse
|
|
7800
8206
|
);
|
|
7801
8207
|
if (adaptResult.success && adaptResult.adaptedComponent) {
|
|
7802
8208
|
const elapsedTime2 = Date.now() - startTime;
|
|
7803
|
-
logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters`);
|
|
7804
|
-
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
7805
|
-
logCollector?.info(`\u2713 UI block adapted successfully`);
|
|
7806
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
8209
|
+
logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters (${elapsedTime2}ms)`);
|
|
7807
8210
|
const textResponseToUse = adaptResult.adaptedTextResponse || cachedTextResponse;
|
|
7808
8211
|
if (streamCallback && textResponseToUse) {
|
|
7809
8212
|
logger.info(`[${this.getProviderName()}] Streaming ${adaptResult.adaptedTextResponse ? "adapted" : "cached"} text response to frontend`);
|
|
@@ -7824,65 +8227,57 @@ ${errorMsg}
|
|
|
7824
8227
|
errors: []
|
|
7825
8228
|
};
|
|
7826
8229
|
} else {
|
|
7827
|
-
logger.info(`[${this.getProviderName()}] Could not adapt matched conversation, continuing to category classification`);
|
|
7828
|
-
logCollector?.warn(`Could not adapt matched conversation: ${adaptResult.explanation}`);
|
|
8230
|
+
logger.info(`[${this.getProviderName()}] Could not adapt matched conversation: ${adaptResult.explanation}, continuing to category classification`);
|
|
7829
8231
|
}
|
|
7830
8232
|
}
|
|
7831
8233
|
} else {
|
|
7832
8234
|
logger.info(`[${this.getProviderName()}] No matching previous conversations found, proceeding to category classification`);
|
|
7833
|
-
logCollector?.info("No similar previous conversations found. Proceeding to category classification...");
|
|
7834
8235
|
}
|
|
7835
8236
|
logger.info(`[${this.getProviderName()}] Step 2: Classifying question category...`);
|
|
7836
|
-
logCollector?.info("Step 2: Classifying question category...");
|
|
7837
8237
|
const categoryClassification = await this.classifyQuestionCategory(
|
|
7838
8238
|
userPrompt,
|
|
7839
8239
|
apiKey,
|
|
7840
|
-
logCollector,
|
|
7841
8240
|
conversationHistory,
|
|
7842
8241
|
externalTools
|
|
7843
8242
|
);
|
|
7844
8243
|
logger.info(
|
|
7845
8244
|
`[${this.getProviderName()}] Question classified as: ${categoryClassification.category} (confidence: ${categoryClassification.confidence}%)`
|
|
7846
8245
|
);
|
|
7847
|
-
logCollector?.info(
|
|
7848
|
-
`Category: ${categoryClassification.category} | Confidence: ${categoryClassification.confidence}%`
|
|
7849
|
-
);
|
|
7850
8246
|
let toolsToUse = [];
|
|
7851
8247
|
if (categoryClassification.externalTools && categoryClassification.externalTools.length > 0) {
|
|
7852
|
-
logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed`);
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
toolsToUse = categoryClassification.externalTools?.map((t) => {
|
|
8248
|
+
logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
|
|
8249
|
+
logger.debug(`[${this.getProviderName()}] Raw external tools from classification: ${JSON.stringify(categoryClassification.externalTools, null, 2)}`);
|
|
8250
|
+
toolsToUse = categoryClassification.externalTools.reduce((acc, t) => {
|
|
7856
8251
|
const realTool = externalTools?.find((tool) => tool.id === t.type);
|
|
7857
|
-
|
|
7858
|
-
|
|
8252
|
+
if (!realTool) {
|
|
8253
|
+
logger.warn(`[${this.getProviderName()}] Tool ${t.type} (${t.name}) not found in registered tools - skipping (likely hallucinated)`);
|
|
8254
|
+
return acc;
|
|
8255
|
+
}
|
|
8256
|
+
acc.push({
|
|
7859
8257
|
id: t.type,
|
|
7860
8258
|
name: t.name,
|
|
7861
8259
|
description: t.description,
|
|
7862
8260
|
params: t.parameters || {},
|
|
7863
|
-
//
|
|
8261
|
+
// Include execution type info from category classification
|
|
7864
8262
|
executionType: t.executionType || "immediate",
|
|
7865
8263
|
executionReason: t.executionReason || "",
|
|
7866
8264
|
requiredFields: t.requiredFields || [],
|
|
7867
8265
|
userProvidedData: t.userProvidedData || null,
|
|
7868
|
-
//
|
|
7869
|
-
outputSchema: realTool
|
|
7870
|
-
fn:
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
};
|
|
7880
|
-
}) || [];
|
|
8266
|
+
// Include outputSchema from real tool for component config generation
|
|
8267
|
+
outputSchema: realTool.outputSchema,
|
|
8268
|
+
fn: realTool.fn
|
|
8269
|
+
});
|
|
8270
|
+
return acc;
|
|
8271
|
+
}, []);
|
|
8272
|
+
const validCount = toolsToUse.length;
|
|
8273
|
+
const hallucinatedCount = categoryClassification.externalTools.length - validCount;
|
|
8274
|
+
if (hallucinatedCount > 0) {
|
|
8275
|
+
logger.warn(`[${this.getProviderName()}] Filtered out ${hallucinatedCount} hallucinated/non-existent tools, ${validCount} valid tools remaining`);
|
|
8276
|
+
}
|
|
7881
8277
|
}
|
|
7882
8278
|
const textResponse = await this.generateTextResponse(
|
|
7883
8279
|
userPrompt,
|
|
7884
8280
|
apiKey,
|
|
7885
|
-
logCollector,
|
|
7886
8281
|
conversationHistory,
|
|
7887
8282
|
streamCallback,
|
|
7888
8283
|
collections,
|
|
@@ -7893,13 +8288,10 @@ ${errorMsg}
|
|
|
7893
8288
|
);
|
|
7894
8289
|
const elapsedTime = Date.now() - startTime;
|
|
7895
8290
|
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7896
|
-
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7897
8291
|
return textResponse;
|
|
7898
8292
|
} catch (error) {
|
|
7899
8293
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7900
8294
|
logger.error(`[${this.getProviderName()}] Error in handleUserRequest: ${errorMsg}`);
|
|
7901
|
-
logger.debug(`[${this.getProviderName()}] Error details:`, error);
|
|
7902
|
-
logCollector?.error(`Error processing request: ${errorMsg}`);
|
|
7903
8295
|
userPromptErrorLogger.logError(
|
|
7904
8296
|
"handleUserRequest",
|
|
7905
8297
|
error instanceof Error ? error : new Error(errorMsg),
|
|
@@ -7907,7 +8299,6 @@ ${errorMsg}
|
|
|
7907
8299
|
);
|
|
7908
8300
|
const elapsedTime = Date.now() - startTime;
|
|
7909
8301
|
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7910
|
-
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7911
8302
|
return {
|
|
7912
8303
|
success: false,
|
|
7913
8304
|
errors: [errorMsg],
|
|
@@ -7923,7 +8314,7 @@ ${errorMsg}
|
|
|
7923
8314
|
* This helps provide intelligent suggestions for follow-up queries
|
|
7924
8315
|
* For general/conversational questions without components, pass textResponse instead
|
|
7925
8316
|
*/
|
|
7926
|
-
async generateNextQuestions(originalUserPrompt, component, componentData, apiKey,
|
|
8317
|
+
async generateNextQuestions(originalUserPrompt, component, componentData, apiKey, conversationHistory, textResponse) {
|
|
7927
8318
|
const methodStartTime = Date.now();
|
|
7928
8319
|
const methodName = "generateNextQuestions";
|
|
7929
8320
|
const promptPreview = originalUserPrompt.substring(0, 50) + (originalUserPrompt.length > 50 ? "..." : "");
|
|
@@ -7960,7 +8351,7 @@ ${errorMsg}
|
|
|
7960
8351
|
},
|
|
7961
8352
|
{
|
|
7962
8353
|
model: this.getModelForTask("simple"),
|
|
7963
|
-
maxTokens:
|
|
8354
|
+
maxTokens: MAX_TOKENS_NEXT_QUESTIONS,
|
|
7964
8355
|
temperature: 0,
|
|
7965
8356
|
apiKey: this.getApiKey(apiKey)
|
|
7966
8357
|
},
|
|
@@ -7968,14 +8359,6 @@ ${errorMsg}
|
|
|
7968
8359
|
// Parse as JSON
|
|
7969
8360
|
);
|
|
7970
8361
|
const nextQuestions = result.nextQuestions || [];
|
|
7971
|
-
logCollector?.logExplanation(
|
|
7972
|
-
"Next questions generated",
|
|
7973
|
-
"Generated intelligent follow-up questions based on component",
|
|
7974
|
-
{
|
|
7975
|
-
count: nextQuestions.length,
|
|
7976
|
-
questions: nextQuestions
|
|
7977
|
-
}
|
|
7978
|
-
);
|
|
7979
8362
|
const methodDuration = Date.now() - methodStartTime;
|
|
7980
8363
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | questions: ${nextQuestions.length}`);
|
|
7981
8364
|
return nextQuestions;
|
|
@@ -7983,8 +8366,6 @@ ${errorMsg}
|
|
|
7983
8366
|
const methodDuration = Date.now() - methodStartTime;
|
|
7984
8367
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7985
8368
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
7986
|
-
logger.debug(`[${this.getProviderName()}] Next questions generation error details:`, error);
|
|
7987
|
-
logCollector?.error(`Error generating next questions: ${errorMsg}`);
|
|
7988
8369
|
return [];
|
|
7989
8370
|
}
|
|
7990
8371
|
}
|
|
@@ -8098,330 +8479,94 @@ function getLLMProviders() {
|
|
|
8098
8479
|
return DEFAULT_PROVIDERS;
|
|
8099
8480
|
}
|
|
8100
8481
|
}
|
|
8101
|
-
var useAnthropicMethod = async (prompt, components, apiKey,
|
|
8102
|
-
logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
|
|
8103
|
-
logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
|
|
8104
|
-
const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8105
|
-
logCollector?.info(msg);
|
|
8482
|
+
var useAnthropicMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8106
8483
|
if (responseMode === "component" && components.length === 0) {
|
|
8107
8484
|
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8108
8485
|
logger.error("[useAnthropicMethod] No components available");
|
|
8109
|
-
logCollector?.error(emptyMsg);
|
|
8110
8486
|
return { success: false, errors: [emptyMsg] };
|
|
8111
8487
|
}
|
|
8112
|
-
|
|
8113
|
-
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8488
|
+
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8114
8489
|
logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
|
|
8115
8490
|
return matchResult;
|
|
8116
8491
|
};
|
|
8117
|
-
var useGroqMethod = async (prompt, components, apiKey,
|
|
8492
|
+
var useGroqMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8118
8493
|
logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
|
|
8119
8494
|
logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
|
|
8120
|
-
const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8121
|
-
logger.info(msg);
|
|
8122
|
-
logCollector?.info(msg);
|
|
8123
|
-
if (responseMode === "component" && components.length === 0) {
|
|
8124
|
-
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8125
|
-
logger.error("[useGroqMethod] No components available");
|
|
8126
|
-
logCollector?.error(emptyMsg);
|
|
8127
|
-
return { success: false, errors: [emptyMsg] };
|
|
8128
|
-
}
|
|
8129
|
-
logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
|
|
8130
|
-
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8131
|
-
logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
|
|
8132
|
-
return matchResult;
|
|
8133
|
-
};
|
|
8134
|
-
var useGeminiMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8135
|
-
logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
|
|
8136
|
-
logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
|
|
8137
|
-
const msg = `Using Gemini LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8138
|
-
logger.info(msg);
|
|
8139
|
-
logCollector?.info(msg);
|
|
8140
|
-
if (responseMode === "component" && components.length === 0) {
|
|
8141
|
-
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8142
|
-
logger.error("[useGeminiMethod] No components available");
|
|
8143
|
-
logCollector?.error(emptyMsg);
|
|
8144
|
-
return { success: false, errors: [emptyMsg] };
|
|
8145
|
-
}
|
|
8146
|
-
logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
|
|
8147
|
-
const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8148
|
-
logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
|
|
8149
|
-
return matchResult;
|
|
8150
|
-
};
|
|
8151
|
-
var useOpenAIMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8152
|
-
logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
|
|
8153
|
-
logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
|
|
8154
|
-
const msg = `Using OpenAI GPT ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8155
|
-
logger.info(msg);
|
|
8156
|
-
logCollector?.info(msg);
|
|
8157
8495
|
if (responseMode === "component" && components.length === 0) {
|
|
8158
8496
|
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8159
|
-
logger.error("[
|
|
8160
|
-
|
|
8161
|
-
return { success: false, errors: [emptyMsg] };
|
|
8162
|
-
}
|
|
8163
|
-
logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
|
|
8164
|
-
const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8165
|
-
logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
|
|
8166
|
-
return matchResult;
|
|
8167
|
-
};
|
|
8168
|
-
var getUserResponseFromCache = async (prompt) => {
|
|
8169
|
-
return false;
|
|
8170
|
-
};
|
|
8171
|
-
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8172
|
-
logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
|
|
8173
|
-
logger.debug(`[get_user_response] Response mode: ${responseMode}`);
|
|
8174
|
-
logger.debug("[get_user_response] Checking cache for existing response");
|
|
8175
|
-
const userResponse = await getUserResponseFromCache(prompt);
|
|
8176
|
-
if (userResponse) {
|
|
8177
|
-
logger.info("[get_user_response] User response found in cache - returning cached result");
|
|
8178
|
-
logCollector?.info("User response found in cache");
|
|
8179
|
-
return {
|
|
8180
|
-
success: true,
|
|
8181
|
-
data: userResponse,
|
|
8182
|
-
errors: []
|
|
8183
|
-
};
|
|
8184
|
-
}
|
|
8185
|
-
logger.debug("[get_user_response] No cached response found, proceeding with LLM providers");
|
|
8186
|
-
const providers = llmProviders || getLLMProviders();
|
|
8187
|
-
const errors = [];
|
|
8188
|
-
const providerOrder = providers.join(", ");
|
|
8189
|
-
logCollector?.info(`LLM Provider order: [${providerOrder}]`);
|
|
8190
|
-
if (conversationHistory && conversationHistory.length > 0) {
|
|
8191
|
-
const exchangeCount = conversationHistory.split("\n").filter((l) => l.startsWith("Q")).length;
|
|
8192
|
-
logger.debug(`[get_user_response] Using conversation history with ${exchangeCount} previous exchanges`);
|
|
8193
|
-
logCollector?.info(`Using conversation history with ${exchangeCount} previous exchanges`);
|
|
8194
|
-
} else {
|
|
8195
|
-
logger.debug("[get_user_response] No conversation history available");
|
|
8196
|
-
}
|
|
8197
|
-
for (let i = 0; i < providers.length; i++) {
|
|
8198
|
-
const provider = providers[i];
|
|
8199
|
-
const isLastProvider = i === providers.length - 1;
|
|
8200
|
-
const attemptMsg = `Attempting provider: ${provider} (${i + 1}/${providers.length})`;
|
|
8201
|
-
logCollector?.info(attemptMsg);
|
|
8202
|
-
let result;
|
|
8203
|
-
if (provider === "anthropic") {
|
|
8204
|
-
result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8205
|
-
} else if (provider === "groq") {
|
|
8206
|
-
result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8207
|
-
} else if (provider === "gemini") {
|
|
8208
|
-
result = await useGeminiMethod(prompt, components, geminiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8209
|
-
} else if (provider === "openai") {
|
|
8210
|
-
result = await useOpenAIMethod(prompt, components, openaiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8211
|
-
} else {
|
|
8212
|
-
logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
|
|
8213
|
-
errors.push(`Unknown provider: ${provider}`);
|
|
8214
|
-
continue;
|
|
8215
|
-
}
|
|
8216
|
-
if (result.success) {
|
|
8217
|
-
const successMsg = `Success with provider: ${provider}`;
|
|
8218
|
-
logger.info(`${successMsg}`);
|
|
8219
|
-
logCollector?.info(successMsg);
|
|
8220
|
-
return result;
|
|
8221
|
-
} else {
|
|
8222
|
-
const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
|
|
8223
|
-
errors.push(...providerErrors);
|
|
8224
|
-
const warnMsg = `Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`;
|
|
8225
|
-
logger.warn(`[get_user_response] ${warnMsg}`);
|
|
8226
|
-
logCollector?.warn(warnMsg);
|
|
8227
|
-
if (!isLastProvider) {
|
|
8228
|
-
const fallbackMsg = "Falling back to next provider...";
|
|
8229
|
-
logger.info(`[get_user_response] ${fallbackMsg}`);
|
|
8230
|
-
logCollector?.info(fallbackMsg);
|
|
8231
|
-
}
|
|
8232
|
-
}
|
|
8233
|
-
}
|
|
8234
|
-
const failureMsg = `All LLM providers failed`;
|
|
8235
|
-
logger.error(`[get_user_response] ${failureMsg}. Errors: ${errors.join("; ")}`);
|
|
8236
|
-
logCollector?.error(`${failureMsg}. Errors: ${errors.join("; ")}`);
|
|
8237
|
-
return {
|
|
8238
|
-
success: false,
|
|
8239
|
-
errors
|
|
8240
|
-
};
|
|
8241
|
-
};
|
|
8242
|
-
|
|
8243
|
-
// src/utils/log-collector.ts
|
|
8244
|
-
var LOG_LEVEL_PRIORITY2 = {
|
|
8245
|
-
errors: 0,
|
|
8246
|
-
warnings: 1,
|
|
8247
|
-
info: 2,
|
|
8248
|
-
verbose: 3
|
|
8249
|
-
};
|
|
8250
|
-
var MESSAGE_LEVEL_PRIORITY2 = {
|
|
8251
|
-
error: 0,
|
|
8252
|
-
warn: 1,
|
|
8253
|
-
info: 2,
|
|
8254
|
-
debug: 3
|
|
8255
|
-
};
|
|
8256
|
-
var UILogCollector = class {
|
|
8257
|
-
constructor(clientId, sendMessage, uiBlockId) {
|
|
8258
|
-
this.logs = [];
|
|
8259
|
-
this.uiBlockId = uiBlockId || null;
|
|
8260
|
-
this.clientId = clientId;
|
|
8261
|
-
this.sendMessage = sendMessage;
|
|
8262
|
-
this.currentLogLevel = logger.getLogLevel();
|
|
8263
|
-
}
|
|
8264
|
-
/**
|
|
8265
|
-
* Check if logging is enabled (uiBlockId is provided)
|
|
8266
|
-
*/
|
|
8267
|
-
isEnabled() {
|
|
8268
|
-
return this.uiBlockId !== null;
|
|
8269
|
-
}
|
|
8270
|
-
/**
|
|
8271
|
-
* Check if a message should be logged based on current log level
|
|
8272
|
-
*/
|
|
8273
|
-
shouldLog(messageLevel) {
|
|
8274
|
-
const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
|
|
8275
|
-
const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
|
|
8276
|
-
return messagePriority <= currentLevelPriority;
|
|
8277
|
-
}
|
|
8278
|
-
/**
|
|
8279
|
-
* Add a log entry with timestamp and immediately send to runtime
|
|
8280
|
-
* Only logs that pass the log level filter are captured and sent
|
|
8281
|
-
*/
|
|
8282
|
-
addLog(level, message, type, data) {
|
|
8283
|
-
if (!this.shouldLog(level)) {
|
|
8284
|
-
return;
|
|
8285
|
-
}
|
|
8286
|
-
const log = {
|
|
8287
|
-
timestamp: Date.now(),
|
|
8288
|
-
level,
|
|
8289
|
-
message,
|
|
8290
|
-
...type && { type },
|
|
8291
|
-
...data && { data }
|
|
8292
|
-
};
|
|
8293
|
-
this.logs.push(log);
|
|
8294
|
-
this.sendLogImmediately(log);
|
|
8295
|
-
switch (level) {
|
|
8296
|
-
case "error":
|
|
8297
|
-
logger.error("UILogCollector:", log);
|
|
8298
|
-
break;
|
|
8299
|
-
case "warn":
|
|
8300
|
-
logger.warn("UILogCollector:", log);
|
|
8301
|
-
break;
|
|
8302
|
-
case "info":
|
|
8303
|
-
logger.info("UILogCollector:", log);
|
|
8304
|
-
break;
|
|
8305
|
-
case "debug":
|
|
8306
|
-
logger.debug("UILogCollector:", log);
|
|
8307
|
-
break;
|
|
8308
|
-
}
|
|
8309
|
-
}
|
|
8310
|
-
/**
|
|
8311
|
-
* Send a single log to runtime immediately
|
|
8312
|
-
*/
|
|
8313
|
-
sendLogImmediately(log) {
|
|
8314
|
-
if (!this.isEnabled()) {
|
|
8315
|
-
return;
|
|
8316
|
-
}
|
|
8317
|
-
const response = {
|
|
8318
|
-
id: this.uiBlockId,
|
|
8319
|
-
type: "UI_LOGS",
|
|
8320
|
-
from: { type: "data-agent" },
|
|
8321
|
-
to: {
|
|
8322
|
-
type: "runtime",
|
|
8323
|
-
id: this.clientId
|
|
8324
|
-
},
|
|
8325
|
-
payload: {
|
|
8326
|
-
logs: [log]
|
|
8327
|
-
// Send single log in array
|
|
8328
|
-
}
|
|
8329
|
-
};
|
|
8330
|
-
this.sendMessage(response);
|
|
8331
|
-
}
|
|
8332
|
-
/**
|
|
8333
|
-
* Log info message
|
|
8334
|
-
*/
|
|
8335
|
-
info(message, type, data) {
|
|
8336
|
-
if (this.isEnabled()) {
|
|
8337
|
-
this.addLog("info", message, type, data);
|
|
8338
|
-
}
|
|
8339
|
-
}
|
|
8340
|
-
/**
|
|
8341
|
-
* Log error message
|
|
8342
|
-
*/
|
|
8343
|
-
error(message, type, data) {
|
|
8344
|
-
if (this.isEnabled()) {
|
|
8345
|
-
this.addLog("error", message, type, data);
|
|
8346
|
-
}
|
|
8347
|
-
}
|
|
8348
|
-
/**
|
|
8349
|
-
* Log warning message
|
|
8350
|
-
*/
|
|
8351
|
-
warn(message, type, data) {
|
|
8352
|
-
if (this.isEnabled()) {
|
|
8353
|
-
this.addLog("warn", message, type, data);
|
|
8354
|
-
}
|
|
8355
|
-
}
|
|
8356
|
-
/**
|
|
8357
|
-
* Log debug message
|
|
8358
|
-
*/
|
|
8359
|
-
debug(message, type, data) {
|
|
8360
|
-
if (this.isEnabled()) {
|
|
8361
|
-
this.addLog("debug", message, type, data);
|
|
8362
|
-
}
|
|
8363
|
-
}
|
|
8364
|
-
/**
|
|
8365
|
-
* Log LLM explanation with typed metadata
|
|
8366
|
-
*/
|
|
8367
|
-
logExplanation(message, explanation, data) {
|
|
8368
|
-
if (this.isEnabled()) {
|
|
8369
|
-
this.addLog("info", message, "explanation", {
|
|
8370
|
-
explanation,
|
|
8371
|
-
...data
|
|
8372
|
-
});
|
|
8373
|
-
}
|
|
8374
|
-
}
|
|
8375
|
-
/**
|
|
8376
|
-
* Log generated query with typed metadata
|
|
8377
|
-
*/
|
|
8378
|
-
logQuery(message, query, data) {
|
|
8379
|
-
if (this.isEnabled()) {
|
|
8380
|
-
this.addLog("info", message, "query", {
|
|
8381
|
-
query,
|
|
8382
|
-
...data
|
|
8383
|
-
});
|
|
8384
|
-
}
|
|
8385
|
-
}
|
|
8386
|
-
/**
|
|
8387
|
-
* Send all collected logs at once (optional, for final summary)
|
|
8388
|
-
*/
|
|
8389
|
-
sendAllLogs() {
|
|
8390
|
-
if (!this.isEnabled() || this.logs.length === 0) {
|
|
8391
|
-
return;
|
|
8392
|
-
}
|
|
8393
|
-
const response = {
|
|
8394
|
-
id: this.uiBlockId,
|
|
8395
|
-
type: "UI_LOGS",
|
|
8396
|
-
from: { type: "data-agent" },
|
|
8397
|
-
to: {
|
|
8398
|
-
type: "runtime",
|
|
8399
|
-
id: this.clientId
|
|
8400
|
-
},
|
|
8401
|
-
payload: {
|
|
8402
|
-
logs: this.logs
|
|
8403
|
-
}
|
|
8404
|
-
};
|
|
8405
|
-
this.sendMessage(response);
|
|
8406
|
-
}
|
|
8407
|
-
/**
|
|
8408
|
-
* Get all collected logs
|
|
8409
|
-
*/
|
|
8410
|
-
getLogs() {
|
|
8411
|
-
return [...this.logs];
|
|
8497
|
+
logger.error("[useGroqMethod] No components available");
|
|
8498
|
+
return { success: false, errors: [emptyMsg] };
|
|
8412
8499
|
}
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8500
|
+
logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
|
|
8501
|
+
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8502
|
+
logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
|
|
8503
|
+
return matchResult;
|
|
8504
|
+
};
|
|
8505
|
+
var useGeminiMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8506
|
+
logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
|
|
8507
|
+
logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
|
|
8508
|
+
if (responseMode === "component" && components.length === 0) {
|
|
8509
|
+
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8510
|
+
logger.error("[useGeminiMethod] No components available");
|
|
8511
|
+
return { success: false, errors: [emptyMsg] };
|
|
8418
8512
|
}
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8513
|
+
logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
|
|
8514
|
+
const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8515
|
+
logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
|
|
8516
|
+
return matchResult;
|
|
8517
|
+
};
|
|
8518
|
+
var useOpenAIMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8519
|
+
logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
|
|
8520
|
+
logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
|
|
8521
|
+
if (responseMode === "component" && components.length === 0) {
|
|
8522
|
+
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8523
|
+
logger.error("[useOpenAIMethod] No components available");
|
|
8524
|
+
return { success: false, errors: [emptyMsg] };
|
|
8424
8525
|
}
|
|
8526
|
+
logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
|
|
8527
|
+
const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8528
|
+
logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
|
|
8529
|
+
return matchResult;
|
|
8530
|
+
};
|
|
8531
|
+
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8532
|
+
const providers = llmProviders || getLLMProviders();
|
|
8533
|
+
const errors = [];
|
|
8534
|
+
logger.info(`[get_user_response] LLM Provider order: [${providers.join(", ")}]`);
|
|
8535
|
+
for (let i = 0; i < providers.length; i++) {
|
|
8536
|
+
const provider = providers[i];
|
|
8537
|
+
const isLastProvider = i === providers.length - 1;
|
|
8538
|
+
logger.info(`[get_user_response] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
|
|
8539
|
+
let result;
|
|
8540
|
+
if (provider === "anthropic") {
|
|
8541
|
+
result = await useAnthropicMethod(prompt, components, anthropicApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8542
|
+
} else if (provider === "groq") {
|
|
8543
|
+
result = await useGroqMethod(prompt, components, groqApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8544
|
+
} else if (provider === "gemini") {
|
|
8545
|
+
result = await useGeminiMethod(prompt, components, geminiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8546
|
+
} else if (provider === "openai") {
|
|
8547
|
+
result = await useOpenAIMethod(prompt, components, openaiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8548
|
+
} else {
|
|
8549
|
+
logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
|
|
8550
|
+
errors.push(`Unknown provider: ${provider}`);
|
|
8551
|
+
continue;
|
|
8552
|
+
}
|
|
8553
|
+
if (result.success) {
|
|
8554
|
+
logger.info(`[get_user_response] Success with provider: ${provider}`);
|
|
8555
|
+
return result;
|
|
8556
|
+
} else {
|
|
8557
|
+
const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
|
|
8558
|
+
errors.push(...providerErrors);
|
|
8559
|
+
logger.warn(`[get_user_response] Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`);
|
|
8560
|
+
if (!isLastProvider) {
|
|
8561
|
+
logger.info("[get_user_response] Falling back to next provider...");
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
}
|
|
8565
|
+
logger.error(`[get_user_response] All LLM providers failed. Errors: ${errors.join("; ")}`);
|
|
8566
|
+
return {
|
|
8567
|
+
success: false,
|
|
8568
|
+
errors
|
|
8569
|
+
};
|
|
8425
8570
|
};
|
|
8426
8571
|
|
|
8427
8572
|
// src/utils/conversation-saver.ts
|
|
@@ -8554,7 +8699,6 @@ var CONTEXT_CONFIG = {
|
|
|
8554
8699
|
// src/handlers/user-prompt-request.ts
|
|
8555
8700
|
var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools) => {
|
|
8556
8701
|
const errors = [];
|
|
8557
|
-
logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
|
|
8558
8702
|
const parseResult = UserPromptRequestMessageSchema.safeParse(data);
|
|
8559
8703
|
if (!parseResult.success) {
|
|
8560
8704
|
const zodError = parseResult.error;
|
|
@@ -8586,27 +8730,23 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8586
8730
|
if (!prompt) {
|
|
8587
8731
|
errors.push("Prompt not found");
|
|
8588
8732
|
}
|
|
8589
|
-
logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
|
|
8590
8733
|
if (errors.length > 0) {
|
|
8591
8734
|
return { success: false, errors, id, wsId };
|
|
8592
8735
|
}
|
|
8593
|
-
const logCollector = new UILogCollector(wsId, sendMessage, existingUiBlockId);
|
|
8594
8736
|
const threadManager = ThreadManager.getInstance();
|
|
8595
8737
|
let thread = threadManager.getThread(threadId);
|
|
8596
8738
|
if (!thread) {
|
|
8597
8739
|
thread = threadManager.createThread(threadId);
|
|
8598
8740
|
logger.info(`Created new thread: ${threadId}`);
|
|
8599
8741
|
}
|
|
8600
|
-
|
|
8742
|
+
logger.info(`Starting user prompt request with ${components.length} components`);
|
|
8601
8743
|
const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
|
|
8602
8744
|
const responseMode = payload.responseMode || "component";
|
|
8603
|
-
logger.info("responseMode", responseMode);
|
|
8604
8745
|
let streamCallback;
|
|
8605
8746
|
let accumulatedStreamResponse = "";
|
|
8606
8747
|
if (responseMode === "text") {
|
|
8607
8748
|
streamCallback = (chunk) => {
|
|
8608
8749
|
accumulatedStreamResponse += chunk;
|
|
8609
|
-
logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
|
|
8610
8750
|
const streamMessage = {
|
|
8611
8751
|
id: `stream_${existingUiBlockId}`,
|
|
8612
8752
|
// Different ID pattern for streaming
|
|
@@ -8622,7 +8762,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8622
8762
|
}
|
|
8623
8763
|
};
|
|
8624
8764
|
sendMessage(streamMessage);
|
|
8625
|
-
logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
|
|
8626
8765
|
};
|
|
8627
8766
|
}
|
|
8628
8767
|
const userResponse = await get_user_response(
|
|
@@ -8633,7 +8772,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8633
8772
|
geminiApiKey,
|
|
8634
8773
|
openaiApiKey,
|
|
8635
8774
|
llmProviders,
|
|
8636
|
-
logCollector,
|
|
8637
8775
|
conversationHistory,
|
|
8638
8776
|
responseMode,
|
|
8639
8777
|
streamCallback,
|
|
@@ -8641,7 +8779,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8641
8779
|
externalTools,
|
|
8642
8780
|
userId
|
|
8643
8781
|
);
|
|
8644
|
-
|
|
8782
|
+
logger.info("User prompt request completed");
|
|
8645
8783
|
const uiBlockId = existingUiBlockId;
|
|
8646
8784
|
if (!userResponse.success) {
|
|
8647
8785
|
logger.error(`User prompt request failed with errors: ${userResponse.errors.join(", ")}`);
|
|
@@ -8708,9 +8846,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8708
8846
|
logger.info(
|
|
8709
8847
|
`Skipping conversation save - response from exact semantic match (${(semanticSimilarity * 100).toFixed(2)}% similarity)`
|
|
8710
8848
|
);
|
|
8711
|
-
logCollector.info(
|
|
8712
|
-
`Using exact cached result (${(semanticSimilarity * 100).toFixed(2)}% match) - not saving duplicate conversation`
|
|
8713
|
-
);
|
|
8714
8849
|
} else {
|
|
8715
8850
|
const uiBlockData = uiBlock.toJSON();
|
|
8716
8851
|
const saveResult = await saveConversation({
|
|
@@ -8918,7 +9053,7 @@ function sendResponse(id, res, sendMessage, clientId) {
|
|
|
8918
9053
|
}
|
|
8919
9054
|
|
|
8920
9055
|
// src/userResponse/next-questions.ts
|
|
8921
|
-
async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders,
|
|
9056
|
+
async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory) {
|
|
8922
9057
|
try {
|
|
8923
9058
|
logger.debug("[generateNextQuestions] Starting next questions generation");
|
|
8924
9059
|
logger.debug(`[generateNextQuestions] User prompt: "${originalUserPrompt?.substring(0, 50)}..."`);
|
|
@@ -8937,7 +9072,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8937
9072
|
const isLastProvider = i === providers.length - 1;
|
|
8938
9073
|
try {
|
|
8939
9074
|
logger.info(`[generateNextQuestions] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
|
|
8940
|
-
logCollector?.info(`Generating questions with ${provider}...`);
|
|
8941
9075
|
let result = [];
|
|
8942
9076
|
if (provider === "groq") {
|
|
8943
9077
|
logger.debug("[generateNextQuestions] Using Groq LLM for next questions");
|
|
@@ -8946,7 +9080,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8946
9080
|
component,
|
|
8947
9081
|
componentData,
|
|
8948
9082
|
groqApiKey,
|
|
8949
|
-
logCollector,
|
|
8950
9083
|
conversationHistory
|
|
8951
9084
|
);
|
|
8952
9085
|
} else if (provider === "gemini") {
|
|
@@ -8956,7 +9089,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8956
9089
|
component,
|
|
8957
9090
|
componentData,
|
|
8958
9091
|
geminiApiKey,
|
|
8959
|
-
logCollector,
|
|
8960
9092
|
conversationHistory
|
|
8961
9093
|
);
|
|
8962
9094
|
} else if (provider === "openai") {
|
|
@@ -8966,7 +9098,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8966
9098
|
component,
|
|
8967
9099
|
componentData,
|
|
8968
9100
|
openaiApiKey,
|
|
8969
|
-
logCollector,
|
|
8970
9101
|
conversationHistory
|
|
8971
9102
|
);
|
|
8972
9103
|
} else {
|
|
@@ -8976,44 +9107,32 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8976
9107
|
component,
|
|
8977
9108
|
componentData,
|
|
8978
9109
|
anthropicApiKey,
|
|
8979
|
-
logCollector,
|
|
8980
9110
|
conversationHistory
|
|
8981
9111
|
);
|
|
8982
9112
|
}
|
|
8983
9113
|
if (result && result.length > 0) {
|
|
8984
9114
|
logger.info(`[generateNextQuestions] Successfully generated ${result.length} questions with ${provider}`);
|
|
8985
9115
|
logger.debug(`[generateNextQuestions] Questions: ${JSON.stringify(result)}`);
|
|
8986
|
-
logCollector?.info(`Generated ${result.length} follow-up questions`);
|
|
8987
9116
|
return result;
|
|
8988
9117
|
}
|
|
8989
|
-
|
|
8990
|
-
logger.warn(`[generateNextQuestions] ${warnMsg}`);
|
|
8991
|
-
if (!isLastProvider) {
|
|
8992
|
-
logCollector?.warn(warnMsg);
|
|
8993
|
-
}
|
|
9118
|
+
logger.warn(`[generateNextQuestions] No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`);
|
|
8994
9119
|
} catch (providerError) {
|
|
8995
9120
|
const errorMsg = providerError instanceof Error ? providerError.message : String(providerError);
|
|
8996
9121
|
logger.error(`[generateNextQuestions] Provider ${provider} failed: ${errorMsg}`);
|
|
8997
9122
|
logger.debug(`[generateNextQuestions] Provider error details:`, providerError);
|
|
8998
9123
|
if (!isLastProvider) {
|
|
8999
|
-
|
|
9000
|
-
logger.info(`[generateNextQuestions] ${fallbackMsg}`);
|
|
9001
|
-
logCollector?.warn(fallbackMsg);
|
|
9002
|
-
} else {
|
|
9003
|
-
logCollector?.error(`Failed to generate questions with ${provider}`);
|
|
9124
|
+
logger.info(`[generateNextQuestions] Provider ${provider} failed, trying next provider...`);
|
|
9004
9125
|
}
|
|
9005
9126
|
continue;
|
|
9006
9127
|
}
|
|
9007
9128
|
}
|
|
9008
9129
|
logger.warn("[generateNextQuestions] All providers failed or returned no questions");
|
|
9009
|
-
logCollector?.warn("Unable to generate follow-up questions");
|
|
9010
9130
|
return [];
|
|
9011
9131
|
} catch (error) {
|
|
9012
9132
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
9013
9133
|
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
9014
9134
|
logger.error(`[generateNextQuestions] Error generating next questions: ${errorMsg}`);
|
|
9015
9135
|
logger.debug("[generateNextQuestions] Error stack trace:", errorStack);
|
|
9016
|
-
logCollector?.error(`Error generating next questions: ${errorMsg}`);
|
|
9017
9136
|
return [];
|
|
9018
9137
|
}
|
|
9019
9138
|
}
|
|
@@ -9061,9 +9180,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
9061
9180
|
return;
|
|
9062
9181
|
}
|
|
9063
9182
|
logger.info(`[ACTIONS_REQ ${id}] UIBlock retrieved successfully`);
|
|
9064
|
-
logger.debug(`[ACTIONS_REQ ${id}] Creating UILogCollector for uiBlockId: ${uiBlockId}`);
|
|
9065
|
-
const logCollector = new UILogCollector(wsId, sendMessage, uiBlockId);
|
|
9066
|
-
logger.info(`[ACTIONS_REQ ${id}] UILogCollector initialized`);
|
|
9067
9183
|
logger.debug(`[ACTIONS_REQ ${id}] Extracting data from UIBlock`);
|
|
9068
9184
|
const userQuestion = uiBlock.getUserQuestion();
|
|
9069
9185
|
const component = uiBlock.getComponentMetadata();
|
|
@@ -9077,13 +9193,11 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
9077
9193
|
logger.info(`[ACTIONS_REQ ${id}] Conversation history extracted: ${historyLineCount} lines`);
|
|
9078
9194
|
logger.debug(`[ACTIONS_REQ ${id}] Conversation history preview:
|
|
9079
9195
|
${conversationHistory.substring(0, 200)}...`);
|
|
9080
|
-
|
|
9081
|
-
logger.info(`[ACTIONS_REQ ${id}] Generating actions for component: ${component?.name || "unknown"}`);
|
|
9196
|
+
logger.info(`[ACTIONS_REQ ${id}] Generating actions for UIBlock: ${uiBlockId}, component: ${component?.name || "unknown"}`);
|
|
9082
9197
|
logger.debug(`[ACTIONS_REQ ${id}] Checking if actions are already cached`);
|
|
9083
9198
|
const startTime = Date.now();
|
|
9084
9199
|
const actions = await uiBlock.getOrFetchActions(async () => {
|
|
9085
9200
|
logger.info(`[ACTIONS_REQ ${id}] Actions not cached, generating new actions...`);
|
|
9086
|
-
logCollector.info("Generating follow-up questions...");
|
|
9087
9201
|
logger.info(`[ACTIONS_REQ ${id}] Starting next questions generation with ${llmProviders?.join(", ") || "default"} providers`);
|
|
9088
9202
|
const nextQuestions = await generateNextQuestions(
|
|
9089
9203
|
userQuestion,
|
|
@@ -9094,7 +9208,6 @@ ${conversationHistory.substring(0, 200)}...`);
|
|
|
9094
9208
|
geminiApiKey,
|
|
9095
9209
|
openaiApiKey,
|
|
9096
9210
|
llmProviders,
|
|
9097
|
-
logCollector,
|
|
9098
9211
|
conversationHistory
|
|
9099
9212
|
);
|
|
9100
9213
|
logger.info(`[ACTIONS_REQ ${id}] Generated ${nextQuestions.length} questions`);
|
|
@@ -9112,11 +9225,10 @@ ${conversationHistory.substring(0, 200)}...`);
|
|
|
9112
9225
|
const processingTime = Date.now() - startTime;
|
|
9113
9226
|
logger.info(`[ACTIONS_REQ ${id}] Actions retrieved in ${processingTime}ms - ${actions.length} actions total`);
|
|
9114
9227
|
if (actions.length > 0) {
|
|
9115
|
-
|
|
9228
|
+
logger.info(`[ACTIONS_REQ ${id}] Generated ${actions.length} follow-up questions successfully`);
|
|
9116
9229
|
logger.debug(`[ACTIONS_REQ ${id}] Actions: ${actions.map((a) => a.name).join(", ")}`);
|
|
9117
9230
|
} else {
|
|
9118
9231
|
logger.warn(`[ACTIONS_REQ ${id}] No actions generated`);
|
|
9119
|
-
logCollector.warn("No follow-up questions could be generated");
|
|
9120
9232
|
}
|
|
9121
9233
|
logger.debug(`[ACTIONS_REQ ${id}] Sending successful response to client`);
|
|
9122
9234
|
sendResponse2(id, {
|
|
@@ -9135,15 +9247,6 @@ ${conversationHistory.substring(0, 200)}...`);
|
|
|
9135
9247
|
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
9136
9248
|
logger.error(`[ACTIONS_REQ] Failed to handle actions request: ${errorMessage}`);
|
|
9137
9249
|
logger.debug(`[ACTIONS_REQ] Error stack trace:`, errorStack);
|
|
9138
|
-
try {
|
|
9139
|
-
const parsedData = data;
|
|
9140
|
-
if (parsedData?.id && parsedData?.from?.id) {
|
|
9141
|
-
const logCollector = parsedData?.payload?.SA_RUNTIME?.uiBlockId ? new UILogCollector(parsedData.from.id, sendMessage, parsedData.payload.SA_RUNTIME.uiBlockId) : void 0;
|
|
9142
|
-
logCollector?.error(`Failed to generate actions: ${errorMessage}`);
|
|
9143
|
-
}
|
|
9144
|
-
} catch (logError) {
|
|
9145
|
-
logger.debug("[ACTIONS_REQ] Failed to send error logs to UI:", logError);
|
|
9146
|
-
}
|
|
9147
9250
|
sendResponse2(null, {
|
|
9148
9251
|
success: false,
|
|
9149
9252
|
error: errorMessage
|
|
@@ -9724,7 +9827,6 @@ function sendResponse3(id, res, sendMessage, clientId) {
|
|
|
9724
9827
|
var dashboardManager = null;
|
|
9725
9828
|
function setDashboardManager(manager) {
|
|
9726
9829
|
dashboardManager = manager;
|
|
9727
|
-
logger.info("DashboardManager instance set");
|
|
9728
9830
|
}
|
|
9729
9831
|
function getDashboardManager() {
|
|
9730
9832
|
if (!dashboardManager) {
|
|
@@ -13051,6 +13153,190 @@ var ReportManager = class {
|
|
|
13051
13153
|
}
|
|
13052
13154
|
};
|
|
13053
13155
|
|
|
13156
|
+
// src/utils/log-collector.ts
|
|
13157
|
+
var LOG_LEVEL_PRIORITY2 = {
|
|
13158
|
+
errors: 0,
|
|
13159
|
+
warnings: 1,
|
|
13160
|
+
info: 2,
|
|
13161
|
+
verbose: 3
|
|
13162
|
+
};
|
|
13163
|
+
var MESSAGE_LEVEL_PRIORITY2 = {
|
|
13164
|
+
error: 0,
|
|
13165
|
+
warn: 1,
|
|
13166
|
+
info: 2,
|
|
13167
|
+
debug: 3
|
|
13168
|
+
};
|
|
13169
|
+
var UILogCollector = class {
|
|
13170
|
+
constructor(clientId, sendMessage, uiBlockId) {
|
|
13171
|
+
this.logs = [];
|
|
13172
|
+
this.uiBlockId = uiBlockId || null;
|
|
13173
|
+
this.clientId = clientId;
|
|
13174
|
+
this.sendMessage = sendMessage;
|
|
13175
|
+
this.currentLogLevel = logger.getLogLevel();
|
|
13176
|
+
}
|
|
13177
|
+
/**
|
|
13178
|
+
* Check if logging is enabled (uiBlockId is provided)
|
|
13179
|
+
*/
|
|
13180
|
+
isEnabled() {
|
|
13181
|
+
return this.uiBlockId !== null;
|
|
13182
|
+
}
|
|
13183
|
+
/**
|
|
13184
|
+
* Check if a message should be logged based on current log level
|
|
13185
|
+
*/
|
|
13186
|
+
shouldLog(messageLevel) {
|
|
13187
|
+
const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
|
|
13188
|
+
const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
|
|
13189
|
+
return messagePriority <= currentLevelPriority;
|
|
13190
|
+
}
|
|
13191
|
+
/**
|
|
13192
|
+
* Add a log entry with timestamp and immediately send to runtime
|
|
13193
|
+
* Only logs that pass the log level filter are captured and sent
|
|
13194
|
+
*/
|
|
13195
|
+
addLog(level, message, type, data) {
|
|
13196
|
+
if (!this.shouldLog(level)) {
|
|
13197
|
+
return;
|
|
13198
|
+
}
|
|
13199
|
+
const log = {
|
|
13200
|
+
timestamp: Date.now(),
|
|
13201
|
+
level,
|
|
13202
|
+
message,
|
|
13203
|
+
...type && { type },
|
|
13204
|
+
...data && { data }
|
|
13205
|
+
};
|
|
13206
|
+
this.logs.push(log);
|
|
13207
|
+
this.sendLogImmediately(log);
|
|
13208
|
+
switch (level) {
|
|
13209
|
+
case "error":
|
|
13210
|
+
logger.error("UILogCollector:", log);
|
|
13211
|
+
break;
|
|
13212
|
+
case "warn":
|
|
13213
|
+
logger.warn("UILogCollector:", log);
|
|
13214
|
+
break;
|
|
13215
|
+
case "info":
|
|
13216
|
+
logger.info("UILogCollector:", log);
|
|
13217
|
+
break;
|
|
13218
|
+
case "debug":
|
|
13219
|
+
logger.debug("UILogCollector:", log);
|
|
13220
|
+
break;
|
|
13221
|
+
}
|
|
13222
|
+
}
|
|
13223
|
+
/**
|
|
13224
|
+
* Send a single log to runtime immediately
|
|
13225
|
+
*/
|
|
13226
|
+
sendLogImmediately(log) {
|
|
13227
|
+
if (!this.isEnabled()) {
|
|
13228
|
+
return;
|
|
13229
|
+
}
|
|
13230
|
+
const response = {
|
|
13231
|
+
id: this.uiBlockId,
|
|
13232
|
+
type: "UI_LOGS",
|
|
13233
|
+
from: { type: "data-agent" },
|
|
13234
|
+
to: {
|
|
13235
|
+
type: "runtime",
|
|
13236
|
+
id: this.clientId
|
|
13237
|
+
},
|
|
13238
|
+
payload: {
|
|
13239
|
+
logs: [log]
|
|
13240
|
+
// Send single log in array
|
|
13241
|
+
}
|
|
13242
|
+
};
|
|
13243
|
+
this.sendMessage(response);
|
|
13244
|
+
}
|
|
13245
|
+
/**
|
|
13246
|
+
* Log info message
|
|
13247
|
+
*/
|
|
13248
|
+
info(message, type, data) {
|
|
13249
|
+
if (this.isEnabled()) {
|
|
13250
|
+
this.addLog("info", message, type, data);
|
|
13251
|
+
}
|
|
13252
|
+
}
|
|
13253
|
+
/**
|
|
13254
|
+
* Log error message
|
|
13255
|
+
*/
|
|
13256
|
+
error(message, type, data) {
|
|
13257
|
+
if (this.isEnabled()) {
|
|
13258
|
+
this.addLog("error", message, type, data);
|
|
13259
|
+
}
|
|
13260
|
+
}
|
|
13261
|
+
/**
|
|
13262
|
+
* Log warning message
|
|
13263
|
+
*/
|
|
13264
|
+
warn(message, type, data) {
|
|
13265
|
+
if (this.isEnabled()) {
|
|
13266
|
+
this.addLog("warn", message, type, data);
|
|
13267
|
+
}
|
|
13268
|
+
}
|
|
13269
|
+
/**
|
|
13270
|
+
* Log debug message
|
|
13271
|
+
*/
|
|
13272
|
+
debug(message, type, data) {
|
|
13273
|
+
if (this.isEnabled()) {
|
|
13274
|
+
this.addLog("debug", message, type, data);
|
|
13275
|
+
}
|
|
13276
|
+
}
|
|
13277
|
+
/**
|
|
13278
|
+
* Log LLM explanation with typed metadata
|
|
13279
|
+
*/
|
|
13280
|
+
logExplanation(message, explanation, data) {
|
|
13281
|
+
if (this.isEnabled()) {
|
|
13282
|
+
this.addLog("info", message, "explanation", {
|
|
13283
|
+
explanation,
|
|
13284
|
+
...data
|
|
13285
|
+
});
|
|
13286
|
+
}
|
|
13287
|
+
}
|
|
13288
|
+
/**
|
|
13289
|
+
* Log generated query with typed metadata
|
|
13290
|
+
*/
|
|
13291
|
+
logQuery(message, query, data) {
|
|
13292
|
+
if (this.isEnabled()) {
|
|
13293
|
+
this.addLog("info", message, "query", {
|
|
13294
|
+
query,
|
|
13295
|
+
...data
|
|
13296
|
+
});
|
|
13297
|
+
}
|
|
13298
|
+
}
|
|
13299
|
+
/**
|
|
13300
|
+
* Send all collected logs at once (optional, for final summary)
|
|
13301
|
+
*/
|
|
13302
|
+
sendAllLogs() {
|
|
13303
|
+
if (!this.isEnabled() || this.logs.length === 0) {
|
|
13304
|
+
return;
|
|
13305
|
+
}
|
|
13306
|
+
const response = {
|
|
13307
|
+
id: this.uiBlockId,
|
|
13308
|
+
type: "UI_LOGS",
|
|
13309
|
+
from: { type: "data-agent" },
|
|
13310
|
+
to: {
|
|
13311
|
+
type: "runtime",
|
|
13312
|
+
id: this.clientId
|
|
13313
|
+
},
|
|
13314
|
+
payload: {
|
|
13315
|
+
logs: this.logs
|
|
13316
|
+
}
|
|
13317
|
+
};
|
|
13318
|
+
this.sendMessage(response);
|
|
13319
|
+
}
|
|
13320
|
+
/**
|
|
13321
|
+
* Get all collected logs
|
|
13322
|
+
*/
|
|
13323
|
+
getLogs() {
|
|
13324
|
+
return [...this.logs];
|
|
13325
|
+
}
|
|
13326
|
+
/**
|
|
13327
|
+
* Clear all logs
|
|
13328
|
+
*/
|
|
13329
|
+
clearLogs() {
|
|
13330
|
+
this.logs = [];
|
|
13331
|
+
}
|
|
13332
|
+
/**
|
|
13333
|
+
* Set uiBlockId (in case it's provided later)
|
|
13334
|
+
*/
|
|
13335
|
+
setUIBlockId(uiBlockId) {
|
|
13336
|
+
this.uiBlockId = uiBlockId;
|
|
13337
|
+
}
|
|
13338
|
+
};
|
|
13339
|
+
|
|
13054
13340
|
// src/services/cleanup-service.ts
|
|
13055
13341
|
var CleanupService = class _CleanupService {
|
|
13056
13342
|
constructor() {
|
|
@@ -13231,7 +13517,6 @@ var CleanupService = class _CleanupService {
|
|
|
13231
13517
|
};
|
|
13232
13518
|
|
|
13233
13519
|
// src/index.ts
|
|
13234
|
-
var SDK_VERSION = "0.0.8";
|
|
13235
13520
|
var DEFAULT_WS_URL = "wss://ws.superatom.ai/websocket";
|
|
13236
13521
|
var SuperatomSDK = class {
|
|
13237
13522
|
// 3.5 minutes (PING_INTERVAL + 30s grace)
|
|
@@ -13272,7 +13557,7 @@ var SuperatomSDK = class {
|
|
|
13272
13557
|
if (config.queryCacheTTL !== void 0) {
|
|
13273
13558
|
queryCache.setTTL(config.queryCacheTTL);
|
|
13274
13559
|
}
|
|
13275
|
-
logger.info(`Initializing Superatom SDK
|
|
13560
|
+
logger.info(`Initializing Superatom SDK for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}, query cache TTL: ${queryCache.getTTL()} minutes`);
|
|
13276
13561
|
this.userManager = new UserManager(this.projectId, 5e3);
|
|
13277
13562
|
this.dashboardManager = new DashboardManager(this.projectId);
|
|
13278
13563
|
this.reportManager = new ReportManager(this.projectId);
|
|
@@ -13326,7 +13611,6 @@ var SuperatomSDK = class {
|
|
|
13326
13611
|
*/
|
|
13327
13612
|
initializeDashboardManager() {
|
|
13328
13613
|
setDashboardManager(this.dashboardManager);
|
|
13329
|
-
logger.info(`DashboardManager initialized for project: ${this.projectId}`);
|
|
13330
13614
|
}
|
|
13331
13615
|
/**
|
|
13332
13616
|
* Get the DashboardManager instance for this SDK
|
|
@@ -13339,7 +13623,6 @@ var SuperatomSDK = class {
|
|
|
13339
13623
|
*/
|
|
13340
13624
|
initializeReportManager() {
|
|
13341
13625
|
setReportManager(this.reportManager);
|
|
13342
|
-
logger.info(`ReportManager initialized for project: ${this.projectId}`);
|
|
13343
13626
|
}
|
|
13344
13627
|
/**
|
|
13345
13628
|
* Get the ReportManager instance for this SDK
|
|
@@ -13717,7 +14000,6 @@ var SuperatomSDK = class {
|
|
|
13717
14000
|
CONTEXT_CONFIG,
|
|
13718
14001
|
CleanupService,
|
|
13719
14002
|
LLM,
|
|
13720
|
-
SDK_VERSION,
|
|
13721
14003
|
STORAGE_CONFIG,
|
|
13722
14004
|
SuperatomSDK,
|
|
13723
14005
|
Thread,
|