@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.mjs
CHANGED
|
@@ -1458,6 +1458,351 @@ var QueryCache = class {
|
|
|
1458
1458
|
};
|
|
1459
1459
|
var queryCache = new QueryCache();
|
|
1460
1460
|
|
|
1461
|
+
// src/userResponse/llm-result-truncator.ts
|
|
1462
|
+
var DEFAULT_MAX_ROWS = 10;
|
|
1463
|
+
var DEFAULT_MAX_CHARS_PER_FIELD = 500;
|
|
1464
|
+
function inferFieldType(value) {
|
|
1465
|
+
if (value === null || value === void 0) {
|
|
1466
|
+
return "null";
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof value === "string") {
|
|
1469
|
+
if (isDateString(value)) {
|
|
1470
|
+
return "date";
|
|
1471
|
+
}
|
|
1472
|
+
return "string";
|
|
1473
|
+
}
|
|
1474
|
+
if (typeof value === "number") {
|
|
1475
|
+
return "number";
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof value === "boolean") {
|
|
1478
|
+
return "boolean";
|
|
1479
|
+
}
|
|
1480
|
+
if (Array.isArray(value)) {
|
|
1481
|
+
return "array";
|
|
1482
|
+
}
|
|
1483
|
+
if (typeof value === "object") {
|
|
1484
|
+
return "object";
|
|
1485
|
+
}
|
|
1486
|
+
return "unknown";
|
|
1487
|
+
}
|
|
1488
|
+
function isDateString(value) {
|
|
1489
|
+
const datePatterns = [
|
|
1490
|
+
/^\d{4}-\d{2}-\d{2}$/,
|
|
1491
|
+
// YYYY-MM-DD
|
|
1492
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
|
|
1493
|
+
// ISO 8601
|
|
1494
|
+
/^\d{2}\/\d{2}\/\d{4}$/,
|
|
1495
|
+
// MM/DD/YYYY
|
|
1496
|
+
/^\d{4}\/\d{2}\/\d{2}$/
|
|
1497
|
+
// YYYY/MM/DD
|
|
1498
|
+
];
|
|
1499
|
+
return datePatterns.some((pattern) => pattern.test(value));
|
|
1500
|
+
}
|
|
1501
|
+
function truncateTextField(value, maxLength) {
|
|
1502
|
+
if (value.length <= maxLength) {
|
|
1503
|
+
return { text: value, wasTruncated: false };
|
|
1504
|
+
}
|
|
1505
|
+
const truncated = value.substring(0, maxLength);
|
|
1506
|
+
const remaining = value.length - maxLength;
|
|
1507
|
+
return {
|
|
1508
|
+
text: `${truncated}... (${remaining} more chars)`,
|
|
1509
|
+
wasTruncated: true
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
function truncateFieldValue(value, maxCharsPerField) {
|
|
1513
|
+
if (value === null || value === void 0) {
|
|
1514
|
+
return { value, wasTruncated: false };
|
|
1515
|
+
}
|
|
1516
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1517
|
+
return { value, wasTruncated: false };
|
|
1518
|
+
}
|
|
1519
|
+
if (typeof value === "string") {
|
|
1520
|
+
const result2 = truncateTextField(value, maxCharsPerField);
|
|
1521
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1522
|
+
}
|
|
1523
|
+
if (Array.isArray(value)) {
|
|
1524
|
+
if (value.length === 0) {
|
|
1525
|
+
return { value: [], wasTruncated: false };
|
|
1526
|
+
}
|
|
1527
|
+
const preview = value.slice(0, 3);
|
|
1528
|
+
const hasMore = value.length > 3;
|
|
1529
|
+
return {
|
|
1530
|
+
value: hasMore ? `[${preview.join(", ")}... (${value.length} items)]` : value,
|
|
1531
|
+
wasTruncated: hasMore
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
if (typeof value === "object") {
|
|
1535
|
+
const jsonStr = JSON.stringify(value);
|
|
1536
|
+
const result2 = truncateTextField(jsonStr, maxCharsPerField);
|
|
1537
|
+
return { value: result2.text, wasTruncated: result2.wasTruncated };
|
|
1538
|
+
}
|
|
1539
|
+
const strValue = String(value);
|
|
1540
|
+
const result = truncateTextField(strValue, maxCharsPerField);
|
|
1541
|
+
return { value: result.text, wasTruncated: result.wasTruncated };
|
|
1542
|
+
}
|
|
1543
|
+
function truncateRow(row, maxCharsPerField) {
|
|
1544
|
+
const truncatedRow = {};
|
|
1545
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1546
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1547
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1548
|
+
truncatedRow[key] = result.value;
|
|
1549
|
+
if (result.wasTruncated) {
|
|
1550
|
+
truncatedFields.add(key);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return { row: truncatedRow, truncatedFields };
|
|
1554
|
+
}
|
|
1555
|
+
function extractMetadataFromObject(obj, dataKey, maxCharsPerField) {
|
|
1556
|
+
const metadata = {};
|
|
1557
|
+
const truncatedFields = /* @__PURE__ */ new Set();
|
|
1558
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1559
|
+
if (key === dataKey) {
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (Array.isArray(value)) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
const result = truncateFieldValue(value, maxCharsPerField);
|
|
1566
|
+
metadata[key] = result.value;
|
|
1567
|
+
if (result.wasTruncated) {
|
|
1568
|
+
truncatedFields.add(key);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return { metadata, truncatedFields };
|
|
1572
|
+
}
|
|
1573
|
+
function extractSchema(data, truncatedFields = /* @__PURE__ */ new Set()) {
|
|
1574
|
+
if (!data || data.length === 0) {
|
|
1575
|
+
return [];
|
|
1576
|
+
}
|
|
1577
|
+
const firstRow = data[0];
|
|
1578
|
+
const schema2 = [];
|
|
1579
|
+
for (const [name, value] of Object.entries(firstRow)) {
|
|
1580
|
+
schema2.push({
|
|
1581
|
+
name,
|
|
1582
|
+
type: inferFieldType(value),
|
|
1583
|
+
truncated: truncatedFields.has(name) ? true : void 0
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
return schema2;
|
|
1587
|
+
}
|
|
1588
|
+
function truncateDataArray(data, options = {}) {
|
|
1589
|
+
const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1590
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1591
|
+
if (!data || !Array.isArray(data)) {
|
|
1592
|
+
return {
|
|
1593
|
+
data: [],
|
|
1594
|
+
totalRecords: 0,
|
|
1595
|
+
recordsShown: 0,
|
|
1596
|
+
truncatedFields: /* @__PURE__ */ new Set()
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
const totalRecords = data.length;
|
|
1600
|
+
const rowsToProcess = data.slice(0, maxRows);
|
|
1601
|
+
const truncatedData = [];
|
|
1602
|
+
const allTruncatedFields = /* @__PURE__ */ new Set();
|
|
1603
|
+
for (const row of rowsToProcess) {
|
|
1604
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(row, maxCharsPerField);
|
|
1605
|
+
truncatedData.push(truncatedRow);
|
|
1606
|
+
for (const field of truncatedFields) {
|
|
1607
|
+
allTruncatedFields.add(field);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return {
|
|
1611
|
+
data: truncatedData,
|
|
1612
|
+
totalRecords,
|
|
1613
|
+
recordsShown: truncatedData.length,
|
|
1614
|
+
truncatedFields: allTruncatedFields
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function buildTruncationNote(totalRecords, recordsShown, truncatedFields, maxCharsPerField, sourceName) {
|
|
1618
|
+
const parts = [];
|
|
1619
|
+
if (totalRecords > recordsShown) {
|
|
1620
|
+
const source = sourceName ? ` from ${sourceName}` : "";
|
|
1621
|
+
parts.push(`Showing ${recordsShown} of ${totalRecords} total records${source}`);
|
|
1622
|
+
}
|
|
1623
|
+
if (truncatedFields.size > 0) {
|
|
1624
|
+
const fieldList = Array.from(truncatedFields).join(", ");
|
|
1625
|
+
parts.push(`Fields truncated to ${maxCharsPerField} chars: ${fieldList}`);
|
|
1626
|
+
}
|
|
1627
|
+
return parts.length > 0 ? parts.join(". ") + "." : null;
|
|
1628
|
+
}
|
|
1629
|
+
function formatQueryResultForLLM(data, options = {}) {
|
|
1630
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1631
|
+
if (!Array.isArray(data)) {
|
|
1632
|
+
if (data !== null && data !== void 0) {
|
|
1633
|
+
return {
|
|
1634
|
+
summary: {
|
|
1635
|
+
totalRecords: 1,
|
|
1636
|
+
recordsShown: 1,
|
|
1637
|
+
schema: [{ name: "result", type: inferFieldType(data) }]
|
|
1638
|
+
},
|
|
1639
|
+
data: [{ result: data }],
|
|
1640
|
+
truncationNote: null
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
return {
|
|
1644
|
+
summary: {
|
|
1645
|
+
totalRecords: 0,
|
|
1646
|
+
recordsShown: 0,
|
|
1647
|
+
schema: []
|
|
1648
|
+
},
|
|
1649
|
+
data: [],
|
|
1650
|
+
truncationNote: null
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
const {
|
|
1654
|
+
data: truncatedData,
|
|
1655
|
+
totalRecords,
|
|
1656
|
+
recordsShown,
|
|
1657
|
+
truncatedFields
|
|
1658
|
+
} = truncateDataArray(data, options);
|
|
1659
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1660
|
+
const truncationNote = buildTruncationNote(
|
|
1661
|
+
totalRecords,
|
|
1662
|
+
recordsShown,
|
|
1663
|
+
truncatedFields,
|
|
1664
|
+
maxCharsPerField,
|
|
1665
|
+
"query"
|
|
1666
|
+
);
|
|
1667
|
+
return {
|
|
1668
|
+
summary: {
|
|
1669
|
+
totalRecords,
|
|
1670
|
+
recordsShown,
|
|
1671
|
+
schema: schema2
|
|
1672
|
+
},
|
|
1673
|
+
data: truncatedData,
|
|
1674
|
+
truncationNote
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
function formatToolResultForLLM(result, options = {}) {
|
|
1678
|
+
const { toolName, toolLimit } = options;
|
|
1679
|
+
const effectiveMaxRows = toolLimit ?? options.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1680
|
+
const maxCharsPerField = options.maxCharsPerField ?? DEFAULT_MAX_CHARS_PER_FIELD;
|
|
1681
|
+
if (result === null || result === void 0) {
|
|
1682
|
+
return {
|
|
1683
|
+
toolName,
|
|
1684
|
+
summary: {
|
|
1685
|
+
totalRecords: 0,
|
|
1686
|
+
recordsShown: 0,
|
|
1687
|
+
schema: []
|
|
1688
|
+
},
|
|
1689
|
+
data: [],
|
|
1690
|
+
truncationNote: null
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
if (typeof result === "string") {
|
|
1694
|
+
const { text, wasTruncated } = truncateTextField(result, maxCharsPerField);
|
|
1695
|
+
return {
|
|
1696
|
+
toolName,
|
|
1697
|
+
summary: {
|
|
1698
|
+
totalRecords: 1,
|
|
1699
|
+
recordsShown: 1,
|
|
1700
|
+
schema: [{ name: "result", type: "string", truncated: wasTruncated || void 0 }]
|
|
1701
|
+
},
|
|
1702
|
+
data: [{ result: text }],
|
|
1703
|
+
truncationNote: wasTruncated ? `Result truncated to ${maxCharsPerField} chars.` : null
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
if (Array.isArray(result)) {
|
|
1707
|
+
const {
|
|
1708
|
+
data: truncatedData,
|
|
1709
|
+
totalRecords,
|
|
1710
|
+
recordsShown,
|
|
1711
|
+
truncatedFields
|
|
1712
|
+
} = truncateDataArray(result, {
|
|
1713
|
+
maxRows: effectiveMaxRows,
|
|
1714
|
+
maxCharsPerField
|
|
1715
|
+
});
|
|
1716
|
+
const schema2 = extractSchema(truncatedData, truncatedFields);
|
|
1717
|
+
const truncationNote = buildTruncationNote(
|
|
1718
|
+
totalRecords,
|
|
1719
|
+
recordsShown,
|
|
1720
|
+
truncatedFields,
|
|
1721
|
+
maxCharsPerField,
|
|
1722
|
+
toolName
|
|
1723
|
+
);
|
|
1724
|
+
return {
|
|
1725
|
+
toolName,
|
|
1726
|
+
summary: {
|
|
1727
|
+
totalRecords,
|
|
1728
|
+
recordsShown,
|
|
1729
|
+
schema: schema2
|
|
1730
|
+
},
|
|
1731
|
+
data: truncatedData,
|
|
1732
|
+
truncationNote
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
if (typeof result === "object") {
|
|
1736
|
+
const objResult = result;
|
|
1737
|
+
const dataWrapperKeys = ["data", "results", "items", "records", "rows", "list"];
|
|
1738
|
+
for (const key of dataWrapperKeys) {
|
|
1739
|
+
if (Array.isArray(objResult[key])) {
|
|
1740
|
+
const innerData = objResult[key];
|
|
1741
|
+
const {
|
|
1742
|
+
data: truncatedData,
|
|
1743
|
+
totalRecords,
|
|
1744
|
+
recordsShown,
|
|
1745
|
+
truncatedFields: dataTruncatedFields
|
|
1746
|
+
} = truncateDataArray(innerData, {
|
|
1747
|
+
maxRows: effectiveMaxRows,
|
|
1748
|
+
maxCharsPerField
|
|
1749
|
+
});
|
|
1750
|
+
const {
|
|
1751
|
+
metadata,
|
|
1752
|
+
truncatedFields: metadataTruncatedFields
|
|
1753
|
+
} = extractMetadataFromObject(objResult, key, maxCharsPerField);
|
|
1754
|
+
const allTruncatedFields = /* @__PURE__ */ new Set([...dataTruncatedFields, ...metadataTruncatedFields]);
|
|
1755
|
+
const schema3 = extractSchema(truncatedData, dataTruncatedFields);
|
|
1756
|
+
const truncationNote2 = buildTruncationNote(
|
|
1757
|
+
totalRecords,
|
|
1758
|
+
recordsShown,
|
|
1759
|
+
allTruncatedFields,
|
|
1760
|
+
maxCharsPerField,
|
|
1761
|
+
toolName
|
|
1762
|
+
);
|
|
1763
|
+
const hasMetadata = Object.keys(metadata).length > 0;
|
|
1764
|
+
return {
|
|
1765
|
+
toolName,
|
|
1766
|
+
summary: {
|
|
1767
|
+
totalRecords,
|
|
1768
|
+
recordsShown,
|
|
1769
|
+
schema: schema3
|
|
1770
|
+
},
|
|
1771
|
+
...hasMetadata && { metadata },
|
|
1772
|
+
data: truncatedData,
|
|
1773
|
+
truncationNote: truncationNote2
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
const { row: truncatedRow, truncatedFields } = truncateRow(objResult, maxCharsPerField);
|
|
1778
|
+
const schema2 = extractSchema([truncatedRow], truncatedFields);
|
|
1779
|
+
const truncationNote = truncatedFields.size > 0 ? `Fields truncated to ${maxCharsPerField} chars: ${Array.from(truncatedFields).join(", ")}.` : null;
|
|
1780
|
+
return {
|
|
1781
|
+
toolName,
|
|
1782
|
+
summary: {
|
|
1783
|
+
totalRecords: 1,
|
|
1784
|
+
recordsShown: 1,
|
|
1785
|
+
schema: schema2
|
|
1786
|
+
},
|
|
1787
|
+
data: [truncatedRow],
|
|
1788
|
+
truncationNote
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
toolName,
|
|
1793
|
+
summary: {
|
|
1794
|
+
totalRecords: 1,
|
|
1795
|
+
recordsShown: 1,
|
|
1796
|
+
schema: [{ name: "result", type: inferFieldType(result) }]
|
|
1797
|
+
},
|
|
1798
|
+
data: [{ result }],
|
|
1799
|
+
truncationNote: null
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function formatResultAsString(formattedResult) {
|
|
1803
|
+
return JSON.stringify(formattedResult, null, 2);
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1461
1806
|
// src/handlers/data-request.ts
|
|
1462
1807
|
function getQueryCacheKey(query) {
|
|
1463
1808
|
if (typeof query === "string") {
|
|
@@ -1539,15 +1884,25 @@ async function handleDataRequest(data, collections, sendMessage) {
|
|
|
1539
1884
|
}
|
|
1540
1885
|
}
|
|
1541
1886
|
if (uiBlock) {
|
|
1887
|
+
const formattedResult = formatToolResultForLLM(result, {
|
|
1888
|
+
maxRows: 3,
|
|
1889
|
+
// Only need a few sample rows for summary
|
|
1890
|
+
maxCharsPerField: 100
|
|
1891
|
+
// Short truncation for summary
|
|
1892
|
+
});
|
|
1542
1893
|
const dataSummary = {
|
|
1543
1894
|
_dataReceived: true,
|
|
1544
1895
|
_timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1545
1896
|
_collection: collection,
|
|
1546
1897
|
_operation: op,
|
|
1547
|
-
|
|
1898
|
+
_totalRecords: formattedResult.summary.totalRecords,
|
|
1899
|
+
_recordsShown: formattedResult.summary.recordsShown,
|
|
1900
|
+
_metadata: formattedResult.metadata,
|
|
1901
|
+
// Preserve totalItems, totalDeadstockItems, etc.
|
|
1902
|
+
_schema: formattedResult.summary.schema
|
|
1548
1903
|
};
|
|
1549
1904
|
uiBlock.setComponentData(dataSummary);
|
|
1550
|
-
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (
|
|
1905
|
+
logger.info(`Updated UIBlock ${uiBlockId} with data summary from ${collection}.${op} (${formattedResult.summary.totalRecords} total records)`);
|
|
1551
1906
|
} else {
|
|
1552
1907
|
logger.warn(`UIBlock ${uiBlockId} not found in threads`);
|
|
1553
1908
|
}
|
|
@@ -2955,24 +3310,20 @@ If adaptation is not possible or would fundamentally change the component:
|
|
|
2955
3310
|
"dash-comp-picker": {
|
|
2956
3311
|
system: `You are a component selection expert that picks the best dashboard component and generates complete props based on user requests.
|
|
2957
3312
|
|
|
3313
|
+
## CRITICAL - READ FIRST
|
|
3314
|
+
|
|
3315
|
+
1. Your ENTIRE response must be ONLY a raw JSON object - start with { end with }
|
|
3316
|
+
2. DO NOT explain or answer the user's question in natural language
|
|
3317
|
+
3. DO NOT use markdown code blocks (no \`\`\`)
|
|
3318
|
+
4. DO NOT add any text before or after the JSON
|
|
3319
|
+
5. After executing tools (if needed), return JSON with component selection - NOT a text summary of results
|
|
3320
|
+
|
|
2958
3321
|
## Your Task
|
|
2959
3322
|
|
|
2960
3323
|
Analyze the user's request and:
|
|
2961
3324
|
1. **Select the most appropriate component** from the available components list
|
|
2962
|
-
2. **Determine the data source**: Database query OR External tool
|
|
2963
|
-
3. **Generate complete props** for the selected component
|
|
2964
|
-
|
|
2965
|
-
## Available External Tools
|
|
2966
|
-
|
|
2967
|
-
The following external tools are available:
|
|
2968
|
-
|
|
2969
|
-
{{AVAILABLE_TOOLS}}
|
|
2970
|
-
|
|
2971
|
-
When a tool is needed to complete the user's request:
|
|
2972
|
-
1. **Analyze the request** to determine which tool is needed
|
|
2973
|
-
2. **Extract parameters** from the user's question
|
|
2974
|
-
3. **Execute the tool** by calling it with the extracted parameters
|
|
2975
|
-
4. **Use the results** to configure the component (field names for axes, columns, etc.)
|
|
3325
|
+
2. **Determine the data source**: Database query OR External tool (ERP)
|
|
3326
|
+
3. **Generate complete props** for the selected component including the data retrieval/modification method
|
|
2976
3327
|
|
|
2977
3328
|
## Component Selection Rules
|
|
2978
3329
|
|
|
@@ -3002,7 +3353,7 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3002
3353
|
|
|
3003
3354
|
### Use DATABASE when:
|
|
3004
3355
|
- User asks about data that exists in the database schema
|
|
3005
|
-
- Questions about internal business data
|
|
3356
|
+
- Questions about internal business data
|
|
3006
3357
|
- CRUD operations on database tables
|
|
3007
3358
|
|
|
3008
3359
|
### Use EXTERNAL TOOL when:
|
|
@@ -3015,6 +3366,12 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3015
3366
|
|
|
3016
3367
|
**CRITICAL**: Look at each component's "Props Structure" in the available components list. Generate ALL props that the component expects.
|
|
3017
3368
|
|
|
3369
|
+
**CRITICAL: Each component uses EXACTLY ONE data source - never both!**
|
|
3370
|
+
- If using \`query\`, set \`externalTool: null\`
|
|
3371
|
+
- If using \`externalTool\`, set \`query: null\`
|
|
3372
|
+
- NEVER copy placeholder/description text from component metadata as actual values
|
|
3373
|
+
- \`externalTool.parameters\` MUST be an object, never a string
|
|
3374
|
+
|
|
3018
3375
|
### For Data Viewing Components (charts, tables, KPIs):
|
|
3019
3376
|
|
|
3020
3377
|
**Option A: Database Query** (when data is in database)
|
|
@@ -3023,21 +3380,19 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3023
3380
|
"query": {
|
|
3024
3381
|
"sql": "SELECT column1, column2 FROM table WHERE condition = $param LIMIT 32",
|
|
3025
3382
|
"params": { "param": "value" }
|
|
3026
|
-
}
|
|
3383
|
+
},
|
|
3384
|
+
"externalTool": null
|
|
3027
3385
|
}
|
|
3028
3386
|
\`\`\`
|
|
3029
3387
|
|
|
3030
3388
|
**Option B: External Tool** (when data is from ERP/external system)
|
|
3031
3389
|
\`\`\`json
|
|
3032
3390
|
{
|
|
3391
|
+
"query": null,
|
|
3033
3392
|
"externalTool": {
|
|
3034
3393
|
"toolId": "tool_id_from_list",
|
|
3035
3394
|
"toolName": "Tool Display Name",
|
|
3036
|
-
"
|
|
3037
|
-
"params": {
|
|
3038
|
-
"param1": "value1",
|
|
3039
|
-
"param2": "value2"
|
|
3040
|
-
}
|
|
3395
|
+
"parameters": { "param1": "value1", "param2": "value2" }
|
|
3041
3396
|
}
|
|
3042
3397
|
}
|
|
3043
3398
|
\`\`\`
|
|
@@ -3051,6 +3406,7 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3051
3406
|
"sql": "INSERT INTO table (col1, col2) VALUES ($col1, $col2)",
|
|
3052
3407
|
"params": {}
|
|
3053
3408
|
},
|
|
3409
|
+
"externalTool": null,
|
|
3054
3410
|
"fields": [
|
|
3055
3411
|
{ "name": "col1", "type": "text", "required": true },
|
|
3056
3412
|
{ "name": "col2", "type": "number", "required": false }
|
|
@@ -3058,16 +3414,38 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3058
3414
|
}
|
|
3059
3415
|
\`\`\`
|
|
3060
3416
|
|
|
3417
|
+
For UPDATE:
|
|
3418
|
+
\`\`\`json
|
|
3419
|
+
{
|
|
3420
|
+
"query": {
|
|
3421
|
+
"sql": "UPDATE table SET col1 = $col1, col2 = $col2 WHERE id = $id",
|
|
3422
|
+
"params": { "id": "record_id" }
|
|
3423
|
+
},
|
|
3424
|
+
"externalTool": null
|
|
3425
|
+
}
|
|
3426
|
+
\`\`\`
|
|
3427
|
+
|
|
3428
|
+
For DELETE:
|
|
3429
|
+
\`\`\`json
|
|
3430
|
+
{
|
|
3431
|
+
"query": {
|
|
3432
|
+
"sql": "DELETE FROM table WHERE id = $id",
|
|
3433
|
+
"params": { "id": "record_id" }
|
|
3434
|
+
},
|
|
3435
|
+
"externalTool": null,
|
|
3436
|
+
"submitButtonText": "Confirm Delete",
|
|
3437
|
+
"submitButtonColor": "danger"
|
|
3438
|
+
}
|
|
3439
|
+
\`\`\`
|
|
3440
|
+
|
|
3061
3441
|
**Option B: External Tool Mutation**
|
|
3062
3442
|
\`\`\`json
|
|
3063
3443
|
{
|
|
3444
|
+
"query": null,
|
|
3064
3445
|
"externalTool": {
|
|
3065
3446
|
"toolId": "tool_id_from_list",
|
|
3066
3447
|
"toolName": "Tool Display Name",
|
|
3067
|
-
"
|
|
3068
|
-
"params": {
|
|
3069
|
-
"param1": "value_or_placeholder"
|
|
3070
|
-
}
|
|
3448
|
+
"parameters": { "param1": "value_or_placeholder" }
|
|
3071
3449
|
},
|
|
3072
3450
|
"fields": [
|
|
3073
3451
|
{ "name": "param1", "type": "text", "required": true }
|
|
@@ -3082,6 +3460,7 @@ The user prompt may contain an **existing component** to update. Detect this by
|
|
|
3082
3460
|
|
|
3083
3461
|
You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
3084
3462
|
|
|
3463
|
+
\`\`\`json
|
|
3085
3464
|
{
|
|
3086
3465
|
"componentId": "id_from_available_list_or_existing_component_id",
|
|
3087
3466
|
"componentName": "name_of_component",
|
|
@@ -3096,6 +3475,7 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
|
3096
3475
|
// Include all other required props (title, description, config, fields, etc.)
|
|
3097
3476
|
}
|
|
3098
3477
|
}
|
|
3478
|
+
\`\`\`
|
|
3099
3479
|
|
|
3100
3480
|
**CRITICAL:**
|
|
3101
3481
|
- Return ONLY valid JSON (no markdown code blocks, no text before/after)
|
|
@@ -3118,7 +3498,8 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
|
3118
3498
|
|
|
3119
3499
|
---
|
|
3120
3500
|
|
|
3121
|
-
## CONTEXT
|
|
3501
|
+
## CONTEXT
|
|
3502
|
+
`,
|
|
3122
3503
|
user: `{{USER_PROMPT}}`
|
|
3123
3504
|
},
|
|
3124
3505
|
"dash-filter-picker": {
|
|
@@ -3264,9 +3645,7 @@ var PromptLoader = class {
|
|
|
3264
3645
|
this.databaseRulesCache = /* @__PURE__ */ new Map();
|
|
3265
3646
|
this.isInitialized = false;
|
|
3266
3647
|
this.databaseType = "postgresql";
|
|
3267
|
-
logger.debug("Initializing PromptLoader...");
|
|
3268
3648
|
this.promptsDir = config?.promptsDir || path2.join(process.cwd(), ".prompts");
|
|
3269
|
-
logger.debug(`Prompts directory set to: ${this.promptsDir}`);
|
|
3270
3649
|
}
|
|
3271
3650
|
/**
|
|
3272
3651
|
* Load a prompt template from file system OR fallback to hardcoded prompts
|
|
@@ -3280,7 +3659,6 @@ var PromptLoader = class {
|
|
|
3280
3659
|
if (fs3.existsSync(systemPath) && fs3.existsSync(userPath)) {
|
|
3281
3660
|
const system = fs3.readFileSync(systemPath, "utf-8");
|
|
3282
3661
|
const user = fs3.readFileSync(userPath, "utf-8");
|
|
3283
|
-
logger.info(`\u2713 Loaded prompt '${promptName}' from file system: ${this.promptsDir}`);
|
|
3284
3662
|
return { system, user };
|
|
3285
3663
|
}
|
|
3286
3664
|
} catch (error) {
|
|
@@ -3288,7 +3666,6 @@ var PromptLoader = class {
|
|
|
3288
3666
|
}
|
|
3289
3667
|
const hardcodedPrompt = PROMPTS[promptName];
|
|
3290
3668
|
if (hardcodedPrompt) {
|
|
3291
|
-
logger.info(`\u2713 Loaded prompt '${promptName}' from hardcoded fallback`);
|
|
3292
3669
|
return hardcodedPrompt;
|
|
3293
3670
|
}
|
|
3294
3671
|
throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or hardcoded prompts. Available prompts: ${Object.keys(PROMPTS).join(", ")}`);
|
|
@@ -3302,7 +3679,6 @@ var PromptLoader = class {
|
|
|
3302
3679
|
logger.debug("PromptLoader already initialized, skipping...");
|
|
3303
3680
|
return;
|
|
3304
3681
|
}
|
|
3305
|
-
logger.info("Loading prompts into memory...");
|
|
3306
3682
|
const promptTypes = Object.keys(PROMPTS);
|
|
3307
3683
|
for (const promptName of promptTypes) {
|
|
3308
3684
|
try {
|
|
@@ -3314,7 +3690,6 @@ var PromptLoader = class {
|
|
|
3314
3690
|
}
|
|
3315
3691
|
}
|
|
3316
3692
|
this.isInitialized = true;
|
|
3317
|
-
logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
|
|
3318
3693
|
}
|
|
3319
3694
|
/**
|
|
3320
3695
|
* Replace variables in a template string using {{VARIABLE_NAME}} pattern
|
|
@@ -3354,7 +3729,6 @@ var PromptLoader = class {
|
|
|
3354
3729
|
const processedContext = this.replaceVariables(contextMarker + contextPart, variables);
|
|
3355
3730
|
const staticLength = processedStatic.length;
|
|
3356
3731
|
const contextLength = processedContext.length;
|
|
3357
|
-
logger.debug(`\u2713 Prompt caching enabled for '${promptName}' (cached: ${staticLength} chars, dynamic: ${contextLength} chars)`);
|
|
3358
3732
|
return {
|
|
3359
3733
|
system: [
|
|
3360
3734
|
{
|
|
@@ -3391,7 +3765,6 @@ var PromptLoader = class {
|
|
|
3391
3765
|
this.promptsDir = dir;
|
|
3392
3766
|
this.isInitialized = false;
|
|
3393
3767
|
this.promptCache.clear();
|
|
3394
|
-
logger.debug(`Prompts directory changed to: ${dir}`);
|
|
3395
3768
|
}
|
|
3396
3769
|
/**
|
|
3397
3770
|
* Get current prompts directory
|
|
@@ -3419,7 +3792,6 @@ var PromptLoader = class {
|
|
|
3419
3792
|
setDatabaseType(type) {
|
|
3420
3793
|
this.databaseType = type;
|
|
3421
3794
|
this.databaseRulesCache.clear();
|
|
3422
|
-
logger.debug(`Database type set to: ${type}`);
|
|
3423
3795
|
}
|
|
3424
3796
|
/**
|
|
3425
3797
|
* Get current database type
|
|
@@ -3435,7 +3807,6 @@ var PromptLoader = class {
|
|
|
3435
3807
|
*/
|
|
3436
3808
|
async loadDatabaseRules() {
|
|
3437
3809
|
if (this.databaseRulesCache.has(this.databaseType)) {
|
|
3438
|
-
logger.debug(`\u2713 Database rules for '${this.databaseType}' loaded from cache`);
|
|
3439
3810
|
return this.databaseRulesCache.get(this.databaseType);
|
|
3440
3811
|
}
|
|
3441
3812
|
const rulesPath = path2.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
|
|
@@ -3443,7 +3814,6 @@ var PromptLoader = class {
|
|
|
3443
3814
|
if (fs3.existsSync(rulesPath)) {
|
|
3444
3815
|
const rules = fs3.readFileSync(rulesPath, "utf-8");
|
|
3445
3816
|
this.databaseRulesCache.set(this.databaseType, rules);
|
|
3446
|
-
logger.info(`\u2713 Loaded database rules for '${this.databaseType}' from ${rulesPath}`);
|
|
3447
3817
|
return rules;
|
|
3448
3818
|
}
|
|
3449
3819
|
} catch (error) {
|
|
@@ -3719,7 +4089,6 @@ var Schema = class {
|
|
|
3719
4089
|
* @returns Parsed schema object or null if error occurs
|
|
3720
4090
|
*/
|
|
3721
4091
|
getDatabaseSchema() {
|
|
3722
|
-
logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
|
|
3723
4092
|
try {
|
|
3724
4093
|
const dir = path3.dirname(this.schemaFilePath);
|
|
3725
4094
|
if (!fs4.existsSync(dir)) {
|
|
@@ -3988,14 +4357,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
|
|
|
3988
4357
|
Cost: $${entry.costUSD.toFixed(6)} | Time: ${entry.durationMs}ms${toolInfo}${errorInfo}${cacheStatus}
|
|
3989
4358
|
`;
|
|
3990
4359
|
this.logStream?.write(logLine);
|
|
3991
|
-
if (entry.cacheReadTokens && entry.cacheReadTokens > 0) {
|
|
3992
|
-
console.log(`[LLM] \u26A1 CACHE HIT: ${entry.cacheReadTokens.toLocaleString()} tokens read from cache (${entry.method})`);
|
|
3993
|
-
} else if (entry.cacheWriteTokens && entry.cacheWriteTokens > 0) {
|
|
3994
|
-
console.log(`[LLM] \u{1F4DD} CACHE WRITE: ${entry.cacheWriteTokens.toLocaleString()} tokens cached for future requests (${entry.method})`);
|
|
3995
|
-
}
|
|
3996
|
-
if (process.env.SUPERATOM_LOG_LEVEL === "verbose") {
|
|
3997
|
-
console.log("\n[LLM-Usage]", logLine);
|
|
3998
|
-
}
|
|
3999
4360
|
}
|
|
4000
4361
|
/**
|
|
4001
4362
|
* Log session summary (call at end of request)
|
|
@@ -4028,11 +4389,6 @@ Avg Time/Call: ${Math.round(this.sessionStats.totalDurationMs / this.sessionStat
|
|
|
4028
4389
|
|
|
4029
4390
|
`;
|
|
4030
4391
|
this.logStream?.write(summary);
|
|
4031
|
-
console.log("\n[LLM-Usage] Session Summary:");
|
|
4032
|
-
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`);
|
|
4033
|
-
if (hasCaching) {
|
|
4034
|
-
console.log(` Cache: ${this.sessionStats.totalCacheReadTokens.toLocaleString()} read, ${this.sessionStats.totalCacheWriteTokens.toLocaleString()} written | Savings: ~$${cacheReadSavings.toFixed(4)}`);
|
|
4035
|
-
}
|
|
4036
4392
|
}
|
|
4037
4393
|
/**
|
|
4038
4394
|
* Reset session stats (call at start of new user request)
|
|
@@ -4073,7 +4429,6 @@ Format: [TIMESTAMP] [REQUEST_ID] [PROVIDER/MODEL] [METHOD]
|
|
|
4073
4429
|
`;
|
|
4074
4430
|
this.logStream.write(header);
|
|
4075
4431
|
this.resetSession();
|
|
4076
|
-
console.log(`[LLM-Usage] Log file reset for new request: ${this.logPath}`);
|
|
4077
4432
|
} catch (error) {
|
|
4078
4433
|
console.error("[LLM-Usage-Logger] Failed to reset log file:", error);
|
|
4079
4434
|
}
|
|
@@ -5547,21 +5902,20 @@ var getKnowledgeBase = async ({
|
|
|
5547
5902
|
}) => {
|
|
5548
5903
|
try {
|
|
5549
5904
|
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
|
|
5550
|
-
logger.
|
|
5905
|
+
logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
|
|
5551
5906
|
return "";
|
|
5552
5907
|
}
|
|
5553
|
-
logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
|
|
5554
5908
|
const result = await collections["knowledge-base"]["query"]({
|
|
5555
5909
|
prompt,
|
|
5556
5910
|
topK
|
|
5557
5911
|
});
|
|
5558
5912
|
if (!result || !result.content) {
|
|
5559
|
-
logger.
|
|
5913
|
+
logger.warn("[KnowledgeBase] No knowledge base results returned");
|
|
5560
5914
|
return "";
|
|
5561
5915
|
}
|
|
5562
5916
|
logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
|
|
5563
5917
|
if (result.metadata?.sources && result.metadata.sources.length > 0) {
|
|
5564
|
-
logger.
|
|
5918
|
+
logger.warn(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
|
|
5565
5919
|
}
|
|
5566
5920
|
return result.content;
|
|
5567
5921
|
} catch (error) {
|
|
@@ -5576,13 +5930,12 @@ var getGlobalKnowledgeBase = async ({
|
|
|
5576
5930
|
}) => {
|
|
5577
5931
|
try {
|
|
5578
5932
|
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getGlobal"]) {
|
|
5579
|
-
logger.
|
|
5933
|
+
logger.warn("[KnowledgeBase] knowledge-base.getGlobal collection not registered, skipping");
|
|
5580
5934
|
return "";
|
|
5581
5935
|
}
|
|
5582
|
-
logger.info("[KnowledgeBase] Fetching global knowledge base nodes...");
|
|
5583
5936
|
const result = await collections["knowledge-base"]["getGlobal"]({ limit });
|
|
5584
5937
|
if (!result || !result.content) {
|
|
5585
|
-
logger.
|
|
5938
|
+
logger.warn("[KnowledgeBase] No global knowledge base nodes found");
|
|
5586
5939
|
return "";
|
|
5587
5940
|
}
|
|
5588
5941
|
logger.info(`[KnowledgeBase] Retrieved ${result.count || 0} global knowledge base nodes`);
|
|
@@ -5600,14 +5953,13 @@ var getUserKnowledgeBase = async ({
|
|
|
5600
5953
|
}) => {
|
|
5601
5954
|
try {
|
|
5602
5955
|
if (!userId) {
|
|
5603
|
-
logger.
|
|
5956
|
+
logger.warn("[KnowledgeBase] No userId provided, skipping user knowledge base");
|
|
5604
5957
|
return "";
|
|
5605
5958
|
}
|
|
5606
5959
|
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["getByUser"]) {
|
|
5607
|
-
logger.
|
|
5960
|
+
logger.warn("[KnowledgeBase] knowledge-base.getByUser collection not registered, skipping");
|
|
5608
5961
|
return "";
|
|
5609
5962
|
}
|
|
5610
|
-
logger.info(`[KnowledgeBase] Fetching user knowledge base nodes for userId: ${userId}...`);
|
|
5611
5963
|
const result = await collections["knowledge-base"]["getByUser"]({
|
|
5612
5964
|
userId: Number(userId),
|
|
5613
5965
|
limit
|
|
@@ -5630,7 +5982,6 @@ var getAllKnowledgeBase = async ({
|
|
|
5630
5982
|
userId,
|
|
5631
5983
|
topK = 3
|
|
5632
5984
|
}) => {
|
|
5633
|
-
logger.info("[KnowledgeBase] Fetching all knowledge base contexts...");
|
|
5634
5985
|
const [globalContext, userContext, queryContext] = await Promise.all([
|
|
5635
5986
|
getGlobalKnowledgeBase({ collections }),
|
|
5636
5987
|
getUserKnowledgeBase({ collections, userId }),
|
|
@@ -5652,7 +6003,6 @@ var getAllKnowledgeBase = async ({
|
|
|
5652
6003
|
combinedContext += "The following information is semantically relevant to the current query:\n\n";
|
|
5653
6004
|
combinedContext += queryContext + "\n\n";
|
|
5654
6005
|
}
|
|
5655
|
-
logger.info(`[KnowledgeBase] Combined knowledge base context: global=${globalContext.length} chars, user=${userContext.length} chars, query=${queryContext.length} chars`);
|
|
5656
6006
|
return {
|
|
5657
6007
|
globalContext,
|
|
5658
6008
|
userContext,
|
|
@@ -5889,11 +6239,11 @@ var searchConversationsWithReranking = async (options) => {
|
|
|
5889
6239
|
} = options;
|
|
5890
6240
|
try {
|
|
5891
6241
|
if (!collections || !collections["conversation-history"]) {
|
|
5892
|
-
logger.
|
|
6242
|
+
logger.warn("[ConversationSearch] conversation-history collection not registered, skipping");
|
|
5893
6243
|
return null;
|
|
5894
6244
|
}
|
|
5895
6245
|
if (!collections["conversation-history"]["searchMultiple"]) {
|
|
5896
|
-
logger.
|
|
6246
|
+
logger.warn("[ConversationSearch] searchMultiple not available, falling back to standard search");
|
|
5897
6247
|
return searchConversations({
|
|
5898
6248
|
userPrompt,
|
|
5899
6249
|
collections,
|
|
@@ -5901,9 +6251,6 @@ var searchConversationsWithReranking = async (options) => {
|
|
|
5901
6251
|
similarityThreshold
|
|
5902
6252
|
});
|
|
5903
6253
|
}
|
|
5904
|
-
logger.info(`[ConversationSearch] Hybrid search for: "${userPrompt.substring(0, 50)}..."`);
|
|
5905
|
-
logger.info(`[ConversationSearch] Fetching ${rerankCandidates} candidates for reranking`);
|
|
5906
|
-
logger.info(`[ConversationSearch] Weights - Semantic: ${hybridOptions.semanticWeight}, BM25: ${hybridOptions.bm25Weight}`);
|
|
5907
6254
|
const results = await collections["conversation-history"]["searchMultiple"]({
|
|
5908
6255
|
userPrompt,
|
|
5909
6256
|
userId,
|
|
@@ -5944,7 +6291,6 @@ var searchConversationsWithReranking = async (options) => {
|
|
|
5944
6291
|
logger.info(
|
|
5945
6292
|
`[ConversationSearch] \u2713 Found match with semantic score ${(semanticScore * 100).toFixed(2)}%`
|
|
5946
6293
|
);
|
|
5947
|
-
logger.info(` - Returning cached result for: "${matchedUserPrompt}"`);
|
|
5948
6294
|
return {
|
|
5949
6295
|
uiBlock: best.uiBlock,
|
|
5950
6296
|
similarity: semanticScore,
|
|
@@ -5964,97 +6310,335 @@ var ConversationSearch = {
|
|
|
5964
6310
|
};
|
|
5965
6311
|
var conversation_search_default = ConversationSearch;
|
|
5966
6312
|
|
|
5967
|
-
// src/userResponse/
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
5972
|
-
this.defaultLimit = config?.defaultLimit || 10;
|
|
5973
|
-
this.apiKey = config?.apiKey;
|
|
5974
|
-
this.modelStrategy = config?.modelStrategy || "fast";
|
|
5975
|
-
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
|
|
6313
|
+
// src/userResponse/prompt-extractor.ts
|
|
6314
|
+
function extractPromptText(content) {
|
|
6315
|
+
if (content === null || content === void 0) {
|
|
6316
|
+
return "";
|
|
5976
6317
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
6318
|
+
if (typeof content === "string") {
|
|
6319
|
+
return content;
|
|
6320
|
+
}
|
|
6321
|
+
if (Array.isArray(content)) {
|
|
6322
|
+
return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
|
|
6323
|
+
}
|
|
6324
|
+
if (content && typeof content === "object") {
|
|
6325
|
+
return extractObjectText(content);
|
|
6326
|
+
}
|
|
6327
|
+
return String(content);
|
|
6328
|
+
}
|
|
6329
|
+
function extractContentBlockText(item) {
|
|
6330
|
+
if (typeof item === "string") {
|
|
6331
|
+
return item;
|
|
6332
|
+
}
|
|
6333
|
+
if (item && typeof item === "object") {
|
|
6334
|
+
const obj = item;
|
|
6335
|
+
if (typeof obj.text === "string") {
|
|
6336
|
+
return obj.text;
|
|
6337
|
+
}
|
|
6338
|
+
if (typeof obj.content === "string") {
|
|
6339
|
+
return obj.content;
|
|
5991
6340
|
}
|
|
6341
|
+
return JSON.stringify(item, null, 2);
|
|
6342
|
+
}
|
|
6343
|
+
return String(item);
|
|
6344
|
+
}
|
|
6345
|
+
function extractObjectText(obj) {
|
|
6346
|
+
if (typeof obj.text === "string") {
|
|
6347
|
+
return obj.text;
|
|
6348
|
+
}
|
|
6349
|
+
if (typeof obj.content === "string") {
|
|
6350
|
+
return obj.content;
|
|
6351
|
+
}
|
|
6352
|
+
return JSON.stringify(obj, null, 2);
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
// src/userResponse/constants.ts
|
|
6356
|
+
var MAX_QUERY_VALIDATION_RETRIES = 3;
|
|
6357
|
+
var MAX_QUERY_ATTEMPTS = 6;
|
|
6358
|
+
var MAX_TOOL_ATTEMPTS = 3;
|
|
6359
|
+
var STREAM_FLUSH_INTERVAL_MS = 50;
|
|
6360
|
+
var PROGRESS_HEARTBEAT_INTERVAL_MS = 800;
|
|
6361
|
+
var STREAM_DELAY_MS = 50;
|
|
6362
|
+
var STREAM_IMMEDIATE_FLUSH_THRESHOLD = 100;
|
|
6363
|
+
var MAX_TOKENS_QUERY_FIX = 2048;
|
|
6364
|
+
var MAX_TOKENS_COMPONENT_MATCHING = 8192;
|
|
6365
|
+
var MAX_TOKENS_CLASSIFICATION = 1500;
|
|
6366
|
+
var MAX_TOKENS_ADAPTATION = 8192;
|
|
6367
|
+
var MAX_TOKENS_TEXT_RESPONSE = 4e3;
|
|
6368
|
+
var MAX_TOKENS_NEXT_QUESTIONS = 1200;
|
|
6369
|
+
var DEFAULT_MAX_ROWS_FOR_LLM = 10;
|
|
6370
|
+
var DEFAULT_MAX_CHARS_PER_FIELD2 = 500;
|
|
6371
|
+
var STREAM_PREVIEW_MAX_ROWS = 10;
|
|
6372
|
+
var STREAM_PREVIEW_MAX_CHARS = 200;
|
|
6373
|
+
var TOOL_TRACKING_MAX_ROWS = 5;
|
|
6374
|
+
var TOOL_TRACKING_MAX_CHARS = 200;
|
|
6375
|
+
var TOOL_TRACKING_SAMPLE_ROWS = 3;
|
|
6376
|
+
var MAX_COMPONENT_QUERY_LIMIT = 10;
|
|
6377
|
+
var EXACT_MATCH_SIMILARITY_THRESHOLD = 0.99;
|
|
6378
|
+
var DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD = 0.8;
|
|
6379
|
+
var MAX_TOOL_CALLING_ITERATIONS = 20;
|
|
6380
|
+
var KNOWLEDGE_BASE_TOP_K = 3;
|
|
6381
|
+
|
|
6382
|
+
// src/userResponse/stream-buffer.ts
|
|
6383
|
+
var StreamBuffer = class {
|
|
6384
|
+
constructor(callback) {
|
|
6385
|
+
this.buffer = "";
|
|
6386
|
+
this.flushTimer = null;
|
|
6387
|
+
this.fullText = "";
|
|
6388
|
+
this.callback = callback;
|
|
5992
6389
|
}
|
|
5993
6390
|
/**
|
|
5994
|
-
*
|
|
5995
|
-
* @param strategy - 'best', 'fast', or 'balanced'
|
|
6391
|
+
* Check if the buffer has a callback configured
|
|
5996
6392
|
*/
|
|
5997
|
-
|
|
5998
|
-
this.
|
|
5999
|
-
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
6393
|
+
hasCallback() {
|
|
6394
|
+
return !!this.callback;
|
|
6000
6395
|
}
|
|
6001
6396
|
/**
|
|
6002
|
-
* Get
|
|
6003
|
-
* @returns The current model strategy
|
|
6397
|
+
* Get all text that has been written (including already flushed)
|
|
6004
6398
|
*/
|
|
6005
|
-
|
|
6006
|
-
return this.
|
|
6399
|
+
getFullText() {
|
|
6400
|
+
return this.fullText;
|
|
6007
6401
|
}
|
|
6008
6402
|
/**
|
|
6009
|
-
*
|
|
6010
|
-
*
|
|
6403
|
+
* Write a chunk to the buffer
|
|
6404
|
+
* Large chunks or chunks with newlines are flushed immediately
|
|
6405
|
+
* Small chunks are batched and flushed after a short interval
|
|
6406
|
+
*
|
|
6407
|
+
* @param chunk - Text chunk to write
|
|
6011
6408
|
*/
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
this.conversationSimilarityThreshold = 0.8;
|
|
6409
|
+
write(chunk) {
|
|
6410
|
+
this.fullText += chunk;
|
|
6411
|
+
if (!this.callback) {
|
|
6016
6412
|
return;
|
|
6017
6413
|
}
|
|
6018
|
-
this.
|
|
6019
|
-
|
|
6414
|
+
this.buffer += chunk;
|
|
6415
|
+
if (chunk.includes("\n") || chunk.length > STREAM_IMMEDIATE_FLUSH_THRESHOLD) {
|
|
6416
|
+
this.flushNow();
|
|
6417
|
+
} else if (!this.flushTimer) {
|
|
6418
|
+
this.flushTimer = setTimeout(() => this.flushNow(), STREAM_FLUSH_INTERVAL_MS);
|
|
6419
|
+
}
|
|
6020
6420
|
}
|
|
6021
6421
|
/**
|
|
6022
|
-
*
|
|
6023
|
-
*
|
|
6422
|
+
* Flush the buffer immediately
|
|
6423
|
+
* Call this before tool execution or other operations that need clean output
|
|
6024
6424
|
*/
|
|
6025
|
-
|
|
6026
|
-
|
|
6425
|
+
flush() {
|
|
6426
|
+
this.flushNow();
|
|
6027
6427
|
}
|
|
6028
6428
|
/**
|
|
6029
|
-
*
|
|
6429
|
+
* Internal flush implementation
|
|
6030
6430
|
*/
|
|
6031
|
-
|
|
6032
|
-
|
|
6431
|
+
flushNow() {
|
|
6432
|
+
if (this.flushTimer) {
|
|
6433
|
+
clearTimeout(this.flushTimer);
|
|
6434
|
+
this.flushTimer = null;
|
|
6435
|
+
}
|
|
6436
|
+
if (this.buffer && this.callback) {
|
|
6437
|
+
this.callback(this.buffer);
|
|
6438
|
+
this.buffer = "";
|
|
6439
|
+
}
|
|
6033
6440
|
}
|
|
6034
6441
|
/**
|
|
6035
|
-
*
|
|
6036
|
-
*
|
|
6037
|
-
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
6442
|
+
* Clean up resources
|
|
6443
|
+
* Call this when done with the buffer
|
|
6038
6444
|
*/
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6445
|
+
dispose() {
|
|
6446
|
+
this.flush();
|
|
6447
|
+
this.callback = void 0;
|
|
6448
|
+
}
|
|
6449
|
+
};
|
|
6450
|
+
function streamDelay(ms = STREAM_DELAY_MS) {
|
|
6451
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6452
|
+
}
|
|
6453
|
+
async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
|
|
6454
|
+
if (!streamBuffer.hasCallback()) {
|
|
6455
|
+
return operation();
|
|
6456
|
+
}
|
|
6457
|
+
const startTime = Date.now();
|
|
6458
|
+
await streamDelay(30);
|
|
6459
|
+
streamBuffer.write(`\u23F3 ${progressMessage}`);
|
|
6460
|
+
const heartbeatInterval = setInterval(() => {
|
|
6461
|
+
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
6462
|
+
if (elapsedSeconds >= 1) {
|
|
6463
|
+
streamBuffer.write(` (${elapsedSeconds}s)`);
|
|
6464
|
+
}
|
|
6465
|
+
}, intervalMs);
|
|
6466
|
+
try {
|
|
6467
|
+
const result = await operation();
|
|
6468
|
+
return result;
|
|
6469
|
+
} finally {
|
|
6470
|
+
clearInterval(heartbeatInterval);
|
|
6471
|
+
streamBuffer.write("\n\n");
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
|
|
6475
|
+
// src/userResponse/utils/component-props-processor.ts
|
|
6476
|
+
var NUMERIC_CONFIG_KEYS = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6477
|
+
var STRING_CONFIG_KEYS = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6478
|
+
var CONFIG_FIELDS_TO_VALIDATE = [
|
|
6479
|
+
"xAxisKey",
|
|
6480
|
+
"yAxisKey",
|
|
6481
|
+
"valueKey",
|
|
6482
|
+
"nameKey",
|
|
6483
|
+
"labelKey",
|
|
6484
|
+
"groupBy",
|
|
6485
|
+
"aggregationField",
|
|
6486
|
+
"seriesKey",
|
|
6487
|
+
"sizeKey",
|
|
6488
|
+
"xAggregationField",
|
|
6489
|
+
"yAggregationField"
|
|
6490
|
+
];
|
|
6491
|
+
function findMatchingField(fieldName, configKey, validFieldNames, fieldTypes, providerName) {
|
|
6492
|
+
if (!fieldName) return null;
|
|
6493
|
+
const lowerField = fieldName.toLowerCase();
|
|
6494
|
+
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6495
|
+
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6496
|
+
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6497
|
+
const containsMatches = validFieldNames.filter(
|
|
6498
|
+
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6499
|
+
);
|
|
6500
|
+
if (containsMatches.length === 1) return containsMatches[0];
|
|
6501
|
+
if (NUMERIC_CONFIG_KEYS.includes(configKey)) {
|
|
6502
|
+
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6503
|
+
const match = numericFields.find(
|
|
6504
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6505
|
+
);
|
|
6506
|
+
if (match) return match;
|
|
6507
|
+
if (numericFields.length > 0) {
|
|
6508
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6509
|
+
return numericFields[0];
|
|
6043
6510
|
}
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6511
|
+
}
|
|
6512
|
+
if (STRING_CONFIG_KEYS.includes(configKey)) {
|
|
6513
|
+
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6514
|
+
const match = stringFields.find(
|
|
6515
|
+
(f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase())
|
|
6516
|
+
);
|
|
6517
|
+
if (match) return match;
|
|
6518
|
+
if (stringFields.length > 0) {
|
|
6519
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6520
|
+
return stringFields[0];
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
logger.warn(`[${providerName}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6524
|
+
return validFieldNames[0];
|
|
6525
|
+
}
|
|
6526
|
+
function validateConfigFieldNames(config, outputSchema, providerName) {
|
|
6527
|
+
if (!outputSchema?.fields || !config) return config;
|
|
6528
|
+
const validFieldNames = outputSchema.fields.map((f) => f.name);
|
|
6529
|
+
const fieldTypes = outputSchema.fields.reduce((acc, f) => {
|
|
6530
|
+
acc[f.name] = f.type;
|
|
6531
|
+
return acc;
|
|
6532
|
+
}, {});
|
|
6533
|
+
const correctedConfig = { ...config };
|
|
6534
|
+
for (const configKey of CONFIG_FIELDS_TO_VALIDATE) {
|
|
6535
|
+
const fieldValue = correctedConfig[configKey];
|
|
6536
|
+
if (fieldValue && typeof fieldValue === "string") {
|
|
6537
|
+
if (!validFieldNames.includes(fieldValue)) {
|
|
6538
|
+
const correctedField = findMatchingField(fieldValue, configKey, validFieldNames, fieldTypes, providerName);
|
|
6539
|
+
if (correctedField) {
|
|
6540
|
+
logger.warn(`[${providerName}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6541
|
+
correctedConfig[configKey] = correctedField;
|
|
6049
6542
|
}
|
|
6050
6543
|
}
|
|
6051
6544
|
}
|
|
6052
|
-
|
|
6545
|
+
}
|
|
6546
|
+
if (Array.isArray(correctedConfig.series)) {
|
|
6547
|
+
correctedConfig.series = correctedConfig.series.map((s) => {
|
|
6548
|
+
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6549
|
+
const correctedField = findMatchingField(s.dataKey, "yAxisKey", validFieldNames, fieldTypes, providerName);
|
|
6550
|
+
if (correctedField) {
|
|
6551
|
+
logger.warn(`[${providerName}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6552
|
+
return { ...s, dataKey: correctedField };
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
return s;
|
|
6556
|
+
});
|
|
6557
|
+
}
|
|
6558
|
+
return correctedConfig;
|
|
6559
|
+
}
|
|
6560
|
+
function validateExternalTool(externalTool, executedTools, providerName) {
|
|
6561
|
+
if (!externalTool) {
|
|
6562
|
+
return { valid: true };
|
|
6563
|
+
}
|
|
6564
|
+
const toolId = externalTool.toolId;
|
|
6565
|
+
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
6566
|
+
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
6567
|
+
if (!isValidTool) {
|
|
6568
|
+
logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
6569
|
+
return { valid: false };
|
|
6570
|
+
}
|
|
6571
|
+
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6572
|
+
return { valid: true, executedTool };
|
|
6573
|
+
}
|
|
6574
|
+
function validateAndCleanQuery(query, config) {
|
|
6575
|
+
if (!query) {
|
|
6576
|
+
return { query: null, wasModified: false };
|
|
6577
|
+
}
|
|
6578
|
+
let wasModified = false;
|
|
6579
|
+
let cleanedQuery = query;
|
|
6580
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6581
|
+
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6582
|
+
logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6583
|
+
return { query: null, wasModified: true };
|
|
6584
|
+
}
|
|
6585
|
+
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6586
|
+
if (fixed) {
|
|
6587
|
+
logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6588
|
+
wasModified = true;
|
|
6589
|
+
if (typeof cleanedQuery === "string") {
|
|
6590
|
+
cleanedQuery = fixedQuery;
|
|
6591
|
+
} else if (cleanedQuery?.sql) {
|
|
6592
|
+
cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
|
|
6593
|
+
}
|
|
6594
|
+
}
|
|
6595
|
+
if (typeof cleanedQuery === "string") {
|
|
6596
|
+
const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6597
|
+
if (limitedQuery !== cleanedQuery) wasModified = true;
|
|
6598
|
+
cleanedQuery = limitedQuery;
|
|
6599
|
+
} else if (cleanedQuery?.sql) {
|
|
6600
|
+
const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6601
|
+
if (limitedSql !== cleanedQuery.sql) wasModified = true;
|
|
6602
|
+
cleanedQuery = { ...cleanedQuery, sql: limitedSql };
|
|
6603
|
+
}
|
|
6604
|
+
return { query: cleanedQuery, wasModified };
|
|
6605
|
+
}
|
|
6606
|
+
function processComponentProps(props, executedTools, config) {
|
|
6607
|
+
let cleanedProps = { ...props };
|
|
6608
|
+
if (cleanedProps.externalTool) {
|
|
6609
|
+
const { valid, executedTool } = validateExternalTool(
|
|
6610
|
+
cleanedProps.externalTool,
|
|
6611
|
+
executedTools,
|
|
6612
|
+
config.providerName
|
|
6613
|
+
);
|
|
6614
|
+
if (!valid) {
|
|
6615
|
+
cleanedProps.externalTool = null;
|
|
6616
|
+
} else if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6617
|
+
cleanedProps.config = validateConfigFieldNames(
|
|
6618
|
+
cleanedProps.config,
|
|
6619
|
+
executedTool.outputSchema,
|
|
6620
|
+
config.providerName
|
|
6621
|
+
);
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
if (cleanedProps.query) {
|
|
6625
|
+
const { query } = validateAndCleanQuery(cleanedProps.query, config);
|
|
6626
|
+
cleanedProps.query = query;
|
|
6627
|
+
}
|
|
6628
|
+
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6629
|
+
logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
6630
|
+
}
|
|
6631
|
+
return cleanedProps;
|
|
6632
|
+
}
|
|
6633
|
+
|
|
6634
|
+
// src/userResponse/services/query-execution-service.ts
|
|
6635
|
+
var QueryExecutionService = class {
|
|
6636
|
+
constructor(config) {
|
|
6637
|
+
this.config = config;
|
|
6053
6638
|
}
|
|
6054
6639
|
/**
|
|
6055
|
-
* Get the cache key for a query
|
|
6640
|
+
* Get the cache key for a query
|
|
6056
6641
|
* This ensures the cache key matches what the frontend will send
|
|
6057
|
-
* Used for both caching and internal deduplication
|
|
6058
6642
|
*/
|
|
6059
6643
|
getQueryCacheKey(query) {
|
|
6060
6644
|
if (typeof query === "string") {
|
|
@@ -6070,17 +6654,19 @@ var BaseLLM = class {
|
|
|
6070
6654
|
return "";
|
|
6071
6655
|
}
|
|
6072
6656
|
/**
|
|
6073
|
-
* Execute a query against the database
|
|
6657
|
+
* Execute a query against the database
|
|
6074
6658
|
* @param query - The SQL query to execute (string or object with sql/values)
|
|
6075
6659
|
* @param collections - Collections object containing database execute function
|
|
6076
6660
|
* @returns Object with result data and cache key
|
|
6077
|
-
* @throws Error if query execution fails
|
|
6078
6661
|
*/
|
|
6079
|
-
async
|
|
6662
|
+
async executeQuery(query, collections) {
|
|
6080
6663
|
const cacheKey = this.getQueryCacheKey(query);
|
|
6081
6664
|
if (!cacheKey) {
|
|
6082
6665
|
throw new Error("Invalid query format: expected string or object with sql property");
|
|
6083
6666
|
}
|
|
6667
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
6668
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
6669
|
+
}
|
|
6084
6670
|
const result = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6085
6671
|
return { result, cacheKey };
|
|
6086
6672
|
}
|
|
@@ -6088,7 +6674,7 @@ var BaseLLM = class {
|
|
|
6088
6674
|
* Request the LLM to fix a failed SQL query
|
|
6089
6675
|
* @param failedQuery - The query that failed execution
|
|
6090
6676
|
* @param errorMessage - The error message from the failed execution
|
|
6091
|
-
* @param componentContext - Context about the component
|
|
6677
|
+
* @param componentContext - Context about the component
|
|
6092
6678
|
* @param apiKey - Optional API key
|
|
6093
6679
|
* @returns Fixed query string
|
|
6094
6680
|
*/
|
|
@@ -6129,10 +6715,10 @@ Fixed SQL query:`;
|
|
|
6129
6715
|
user: prompt
|
|
6130
6716
|
},
|
|
6131
6717
|
{
|
|
6132
|
-
model: this.getModelForTask("simple"),
|
|
6133
|
-
maxTokens:
|
|
6718
|
+
model: this.config.getModelForTask("simple"),
|
|
6719
|
+
maxTokens: MAX_TOKENS_QUERY_FIX,
|
|
6134
6720
|
temperature: 0,
|
|
6135
|
-
apiKey: this.getApiKey(apiKey)
|
|
6721
|
+
apiKey: this.config.getApiKey(apiKey)
|
|
6136
6722
|
}
|
|
6137
6723
|
);
|
|
6138
6724
|
let fixedQuery = response.trim();
|
|
@@ -6142,39 +6728,576 @@ Fixed SQL query:`;
|
|
|
6142
6728
|
return validatedQuery;
|
|
6143
6729
|
}
|
|
6144
6730
|
/**
|
|
6145
|
-
*
|
|
6146
|
-
*
|
|
6147
|
-
*
|
|
6148
|
-
*
|
|
6149
|
-
* @
|
|
6150
|
-
* @param components - List of available components
|
|
6151
|
-
* @param apiKey - Optional API key
|
|
6152
|
-
* @param logCollector - Optional log collector
|
|
6153
|
-
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
6154
|
-
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
6731
|
+
* Validate a single component's query with retry logic
|
|
6732
|
+
* @param component - The component to validate
|
|
6733
|
+
* @param collections - Collections object containing database execute function
|
|
6734
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6735
|
+
* @returns Validation result with component, query key, and result
|
|
6155
6736
|
*/
|
|
6156
|
-
async
|
|
6157
|
-
const
|
|
6158
|
-
const
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6737
|
+
async validateSingleQuery(component, collections, apiKey) {
|
|
6738
|
+
const query = component.props?.query;
|
|
6739
|
+
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6740
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6741
|
+
let finalQueryKey = originalQueryKey;
|
|
6742
|
+
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6743
|
+
let currentQueryStr = queryStr;
|
|
6744
|
+
let validated = false;
|
|
6745
|
+
let lastError = "";
|
|
6746
|
+
let result = null;
|
|
6747
|
+
let attempts = 0;
|
|
6748
|
+
logger.info(`[${this.config.providerName}] Validating query for component: ${component.name} (${component.type})`);
|
|
6749
|
+
while (attempts < MAX_QUERY_VALIDATION_RETRIES && !validated) {
|
|
6750
|
+
attempts++;
|
|
6751
|
+
try {
|
|
6752
|
+
logger.debug(`[${this.config.providerName}] Query validation attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES} for ${component.name}`);
|
|
6753
|
+
const validationResult = await this.executeQuery(currentQuery, collections);
|
|
6754
|
+
result = validationResult.result;
|
|
6755
|
+
validated = true;
|
|
6756
|
+
queryCache.set(validationResult.cacheKey, result);
|
|
6757
|
+
logger.info(`[${this.config.providerName}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6758
|
+
if (currentQueryStr !== queryStr) {
|
|
6759
|
+
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6760
|
+
component.props = {
|
|
6761
|
+
...component.props,
|
|
6762
|
+
query: fixedQuery
|
|
6763
|
+
};
|
|
6764
|
+
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6765
|
+
logger.info(`[${this.config.providerName}] Updated ${component.name} with fixed query`);
|
|
6766
|
+
}
|
|
6767
|
+
} catch (error) {
|
|
6768
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
6769
|
+
logger.warn(`[${this.config.providerName}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES}): ${lastError}`);
|
|
6770
|
+
if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
|
|
6771
|
+
logger.error(`[${this.config.providerName}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6772
|
+
break;
|
|
6773
|
+
}
|
|
6774
|
+
logger.info(`[${this.config.providerName}] Requesting query fix from LLM for ${component.name}...`);
|
|
6775
|
+
try {
|
|
6776
|
+
const fixedQueryStr = await this.requestQueryFix(
|
|
6777
|
+
currentQueryStr,
|
|
6778
|
+
lastError,
|
|
6779
|
+
{
|
|
6780
|
+
name: component.name,
|
|
6781
|
+
type: component.type,
|
|
6782
|
+
title: component.props?.title
|
|
6783
|
+
},
|
|
6784
|
+
apiKey
|
|
6785
|
+
);
|
|
6786
|
+
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6787
|
+
logger.info(`[${this.config.providerName}] Received fixed query for ${component.name}, retrying...`);
|
|
6788
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6789
|
+
currentQueryStr = limitedFixedQuery;
|
|
6790
|
+
if (typeof currentQuery === "string") {
|
|
6791
|
+
currentQuery = limitedFixedQuery;
|
|
6792
|
+
} else {
|
|
6793
|
+
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6794
|
+
}
|
|
6795
|
+
} else {
|
|
6796
|
+
logger.warn(`[${this.config.providerName}] LLM returned same or empty query, stopping retries`);
|
|
6797
|
+
break;
|
|
6798
|
+
}
|
|
6799
|
+
} catch (fixError) {
|
|
6800
|
+
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6801
|
+
logger.error(`[${this.config.providerName}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6802
|
+
break;
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
if (!validated) {
|
|
6807
|
+
logger.warn(`[${this.config.providerName}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6808
|
+
}
|
|
6809
|
+
return {
|
|
6810
|
+
component: validated ? component : null,
|
|
6811
|
+
queryKey: finalQueryKey,
|
|
6812
|
+
result,
|
|
6813
|
+
validated
|
|
6814
|
+
};
|
|
6815
|
+
}
|
|
6816
|
+
/**
|
|
6817
|
+
* Validate multiple component queries in parallel
|
|
6818
|
+
* @param components - Array of components with potential queries
|
|
6819
|
+
* @param collections - Collections object containing database execute function
|
|
6820
|
+
* @param apiKey - Optional API key for LLM calls
|
|
6821
|
+
* @returns Object with validated components and query results map
|
|
6822
|
+
*/
|
|
6823
|
+
async validateComponentQueries(components, collections, apiKey) {
|
|
6824
|
+
const queryResults = /* @__PURE__ */ new Map();
|
|
6825
|
+
const validatedComponents = [];
|
|
6826
|
+
const componentsWithoutQuery = [];
|
|
6827
|
+
const componentsWithQuery = [];
|
|
6828
|
+
for (const component of components) {
|
|
6829
|
+
if (!component.props?.query) {
|
|
6830
|
+
componentsWithoutQuery.push(component);
|
|
6831
|
+
} else {
|
|
6832
|
+
componentsWithQuery.push(component);
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
validatedComponents.push(...componentsWithoutQuery);
|
|
6836
|
+
if (componentsWithQuery.length === 0) {
|
|
6837
|
+
return { components: validatedComponents, queryResults };
|
|
6838
|
+
}
|
|
6839
|
+
logger.info(`[${this.config.providerName}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6840
|
+
const validationPromises = componentsWithQuery.map(
|
|
6841
|
+
(component) => this.validateSingleQuery(component, collections, apiKey)
|
|
6842
|
+
);
|
|
6843
|
+
const results = await Promise.allSettled(validationPromises);
|
|
6844
|
+
for (let i = 0; i < results.length; i++) {
|
|
6845
|
+
const result = results[i];
|
|
6846
|
+
const component = componentsWithQuery[i];
|
|
6847
|
+
if (result.status === "fulfilled") {
|
|
6848
|
+
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6849
|
+
if (validated && validatedComponent) {
|
|
6850
|
+
validatedComponents.push(validatedComponent);
|
|
6851
|
+
if (queryResult) {
|
|
6852
|
+
queryResults.set(queryKey, queryResult);
|
|
6853
|
+
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
} else {
|
|
6857
|
+
logger.error(`[${this.config.providerName}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
logger.info(`[${this.config.providerName}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6861
|
+
return {
|
|
6862
|
+
components: validatedComponents,
|
|
6863
|
+
queryResults
|
|
6864
|
+
};
|
|
6865
|
+
}
|
|
6866
|
+
};
|
|
6867
|
+
|
|
6868
|
+
// src/userResponse/services/tool-executor-service.ts
|
|
6869
|
+
var ToolExecutorService = class {
|
|
6870
|
+
constructor(config) {
|
|
6871
|
+
this.queryAttempts = /* @__PURE__ */ new Map();
|
|
6872
|
+
this.toolAttempts = /* @__PURE__ */ new Map();
|
|
6873
|
+
this.executedToolsList = [];
|
|
6874
|
+
this.maxAttemptsReached = false;
|
|
6875
|
+
this.config = config;
|
|
6876
|
+
}
|
|
6877
|
+
/**
|
|
6878
|
+
* Reset state for a new execution
|
|
6879
|
+
*/
|
|
6880
|
+
reset() {
|
|
6881
|
+
this.queryAttempts.clear();
|
|
6882
|
+
this.toolAttempts.clear();
|
|
6883
|
+
this.executedToolsList = [];
|
|
6884
|
+
this.maxAttemptsReached = false;
|
|
6885
|
+
}
|
|
6886
|
+
/**
|
|
6887
|
+
* Get list of successfully executed tools
|
|
6888
|
+
*/
|
|
6889
|
+
getExecutedTools() {
|
|
6890
|
+
return this.executedToolsList;
|
|
6891
|
+
}
|
|
6892
|
+
/**
|
|
6893
|
+
* Check if max attempts were reached
|
|
6894
|
+
*/
|
|
6895
|
+
isMaxAttemptsReached() {
|
|
6896
|
+
return this.maxAttemptsReached;
|
|
6897
|
+
}
|
|
6898
|
+
/**
|
|
6899
|
+
* Create a tool handler function for LLM.streamWithTools
|
|
6900
|
+
* @param externalTools - List of available external tools
|
|
6901
|
+
* @returns Tool handler function
|
|
6902
|
+
*/
|
|
6903
|
+
createToolHandler(externalTools) {
|
|
6904
|
+
return async (toolName, toolInput) => {
|
|
6905
|
+
if (toolName === "execute_query") {
|
|
6906
|
+
return this.executeQuery(toolInput);
|
|
6907
|
+
} else {
|
|
6908
|
+
return this.executeExternalTool(toolName, toolInput, externalTools);
|
|
6909
|
+
}
|
|
6910
|
+
};
|
|
6911
|
+
}
|
|
6912
|
+
/**
|
|
6913
|
+
* Execute a SQL query with retry tracking and streaming feedback
|
|
6914
|
+
*/
|
|
6915
|
+
async executeQuery(toolInput) {
|
|
6916
|
+
let sql = toolInput.sql;
|
|
6917
|
+
const params = toolInput.params || {};
|
|
6918
|
+
const reasoning = toolInput.reasoning;
|
|
6919
|
+
const { streamBuffer, collections, providerName } = this.config;
|
|
6920
|
+
sql = ensureQueryLimit(sql, MAX_COMPONENT_QUERY_LIMIT, MAX_COMPONENT_QUERY_LIMIT);
|
|
6921
|
+
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
6922
|
+
const attempts = (this.queryAttempts.get(queryKey) || 0) + 1;
|
|
6923
|
+
this.queryAttempts.set(queryKey, attempts);
|
|
6924
|
+
if (Object.keys(params).length > 0) {
|
|
6925
|
+
logger.info(`[${providerName}] Query params: ${JSON.stringify(params)}`);
|
|
6926
|
+
}
|
|
6927
|
+
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
6928
|
+
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
6929
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
6930
|
+
this.maxAttemptsReached = true;
|
|
6931
|
+
if (streamBuffer.hasCallback()) {
|
|
6932
|
+
streamBuffer.write(`
|
|
6933
|
+
|
|
6934
|
+
\u274C ${errorMsg}
|
|
6935
|
+
|
|
6936
|
+
Please try rephrasing your question or simplifying your request.
|
|
6937
|
+
|
|
6938
|
+
`);
|
|
6939
|
+
}
|
|
6940
|
+
throw new Error(errorMsg);
|
|
6941
|
+
}
|
|
6942
|
+
try {
|
|
6943
|
+
streamBuffer.flush();
|
|
6944
|
+
if (streamBuffer.hasCallback()) {
|
|
6945
|
+
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
6946
|
+
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
6947
|
+
if (attempts === 1) {
|
|
6948
|
+
streamBuffer.write(`
|
|
6949
|
+
|
|
6950
|
+
\u{1F50D} **Analyzing your question...**
|
|
6951
|
+
|
|
6952
|
+
`);
|
|
6953
|
+
await streamDelay();
|
|
6954
|
+
if (reasoning) {
|
|
6955
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
6956
|
+
|
|
6957
|
+
`);
|
|
6958
|
+
await streamDelay();
|
|
6959
|
+
}
|
|
6960
|
+
streamBuffer.write(`\u{1F4DD} **Generated SQL Query:**
|
|
6961
|
+
\`\`\`sql
|
|
6962
|
+
${sql}
|
|
6963
|
+
\`\`\`${paramsDisplay}
|
|
6964
|
+
|
|
6965
|
+
`);
|
|
6966
|
+
await streamDelay();
|
|
6967
|
+
} else {
|
|
6968
|
+
streamBuffer.write(`
|
|
6969
|
+
|
|
6970
|
+
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
6971
|
+
|
|
6972
|
+
`);
|
|
6973
|
+
await streamDelay();
|
|
6974
|
+
if (reasoning) {
|
|
6975
|
+
streamBuffer.write(`\u{1F4AD} ${reasoning}
|
|
6976
|
+
|
|
6977
|
+
`);
|
|
6978
|
+
await streamDelay();
|
|
6979
|
+
}
|
|
6980
|
+
streamBuffer.write(`\u{1F4DD} **Corrected SQL Query:**
|
|
6981
|
+
\`\`\`sql
|
|
6982
|
+
${sql}
|
|
6983
|
+
\`\`\`${paramsDisplay}
|
|
6984
|
+
|
|
6985
|
+
`);
|
|
6986
|
+
await streamDelay();
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
if (!collections?.["database"]?.["execute"]) {
|
|
6990
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
6991
|
+
}
|
|
6992
|
+
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
6993
|
+
const result = await withProgressHeartbeat(
|
|
6994
|
+
() => collections["database"]["execute"](queryPayload),
|
|
6995
|
+
"Executing database query",
|
|
6996
|
+
streamBuffer
|
|
6997
|
+
);
|
|
6998
|
+
const data = result?.data || result;
|
|
6999
|
+
const rowCount = result?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7000
|
+
if (streamBuffer.hasCallback()) {
|
|
7001
|
+
streamBuffer.write(`
|
|
7002
|
+
\u2705 **Query executed successfully!**
|
|
7003
|
+
|
|
7004
|
+
`);
|
|
7005
|
+
await streamDelay();
|
|
7006
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
7007
|
+
const firstRow = data[0];
|
|
7008
|
+
const columns = Object.keys(firstRow);
|
|
7009
|
+
if (data.length === 1 && columns.length === 1) {
|
|
7010
|
+
const value = firstRow[columns[0]];
|
|
7011
|
+
streamBuffer.write(`**Result:** ${value}
|
|
7012
|
+
|
|
7013
|
+
`);
|
|
7014
|
+
await streamDelay();
|
|
7015
|
+
} else if (data.length > 0) {
|
|
7016
|
+
streamBuffer.write(`**Retrieved ${rowCount} rows**
|
|
7017
|
+
|
|
7018
|
+
`);
|
|
7019
|
+
await streamDelay();
|
|
7020
|
+
const streamPreview = formatQueryResultForLLM(data, {
|
|
7021
|
+
maxRows: STREAM_PREVIEW_MAX_ROWS,
|
|
7022
|
+
maxCharsPerField: STREAM_PREVIEW_MAX_CHARS
|
|
7023
|
+
});
|
|
7024
|
+
streamBuffer.write(`<DataTable>${JSON.stringify(streamPreview.data)}</DataTable>
|
|
7025
|
+
|
|
7026
|
+
`);
|
|
7027
|
+
if (streamPreview.truncationNote) {
|
|
7028
|
+
streamBuffer.write(`*${streamPreview.truncationNote}*
|
|
7029
|
+
|
|
7030
|
+
`);
|
|
7031
|
+
}
|
|
7032
|
+
await streamDelay();
|
|
7033
|
+
}
|
|
7034
|
+
} else if (Array.isArray(data) && data.length === 0) {
|
|
7035
|
+
streamBuffer.write(`**No rows returned.**
|
|
7036
|
+
|
|
7037
|
+
`);
|
|
7038
|
+
await streamDelay();
|
|
7039
|
+
}
|
|
7040
|
+
streamBuffer.write(`\u{1F4CA} **Analyzing results...**
|
|
7041
|
+
|
|
7042
|
+
`);
|
|
7043
|
+
}
|
|
7044
|
+
const formattedResult = formatQueryResultForLLM(data, {
|
|
7045
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7046
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7047
|
+
});
|
|
7048
|
+
if (formattedResult.truncationNote) {
|
|
7049
|
+
logger.info(`[${providerName}] Truncation: ${formattedResult.truncationNote}`);
|
|
7050
|
+
}
|
|
7051
|
+
return formatResultAsString(formattedResult);
|
|
7052
|
+
} catch (error) {
|
|
7053
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7054
|
+
logger.error(`[${providerName}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7055
|
+
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7056
|
+
if (streamBuffer.hasCallback()) {
|
|
7057
|
+
streamBuffer.write(`\u274C **Query execution failed:**
|
|
7058
|
+
\`\`\`
|
|
7059
|
+
${errorMsg}
|
|
7060
|
+
\`\`\`
|
|
7061
|
+
|
|
7062
|
+
`);
|
|
7063
|
+
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7064
|
+
streamBuffer.write(`\u{1F527} **Generating corrected query...**
|
|
7065
|
+
|
|
7066
|
+
`);
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
/**
|
|
7073
|
+
* Execute an external tool with retry tracking and streaming feedback
|
|
7074
|
+
*/
|
|
7075
|
+
async executeExternalTool(toolName, toolInput, externalTools) {
|
|
7076
|
+
const { streamBuffer, providerName } = this.config;
|
|
7077
|
+
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7078
|
+
if (!externalTool) {
|
|
7079
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
7080
|
+
}
|
|
7081
|
+
const attempts = (this.toolAttempts.get(toolName) || 0) + 1;
|
|
7082
|
+
this.toolAttempts.set(toolName, attempts);
|
|
7083
|
+
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7084
|
+
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7085
|
+
logger.error(`[${providerName}] ${errorMsg}`);
|
|
7086
|
+
if (streamBuffer.hasCallback()) {
|
|
7087
|
+
streamBuffer.write(`
|
|
7088
|
+
|
|
7089
|
+
\u274C ${errorMsg}
|
|
7090
|
+
|
|
7091
|
+
Please try rephrasing your request or contact support.
|
|
7092
|
+
|
|
7093
|
+
`);
|
|
7094
|
+
}
|
|
7095
|
+
throw new Error(errorMsg);
|
|
7096
|
+
}
|
|
7097
|
+
try {
|
|
7098
|
+
streamBuffer.flush();
|
|
7099
|
+
if (streamBuffer.hasCallback()) {
|
|
7100
|
+
if (attempts === 1) {
|
|
7101
|
+
streamBuffer.write(`
|
|
7102
|
+
|
|
7103
|
+
\u{1F517} **Executing ${externalTool.name}...**
|
|
7104
|
+
|
|
7105
|
+
`);
|
|
7106
|
+
} else {
|
|
7107
|
+
streamBuffer.write(`
|
|
7108
|
+
|
|
7109
|
+
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7110
|
+
|
|
7111
|
+
`);
|
|
7112
|
+
}
|
|
7113
|
+
await streamDelay();
|
|
7114
|
+
}
|
|
7115
|
+
const result = await withProgressHeartbeat(
|
|
7116
|
+
() => externalTool.fn(toolInput),
|
|
7117
|
+
`Running ${externalTool.name}`,
|
|
7118
|
+
streamBuffer
|
|
7119
|
+
);
|
|
7120
|
+
if (!this.executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7121
|
+
const formattedForTracking = formatToolResultForLLM(result, {
|
|
7122
|
+
toolName: externalTool.name,
|
|
7123
|
+
toolLimit: externalTool.limit,
|
|
7124
|
+
maxRows: TOOL_TRACKING_MAX_ROWS,
|
|
7125
|
+
maxCharsPerField: TOOL_TRACKING_MAX_CHARS
|
|
7126
|
+
});
|
|
7127
|
+
this.executedToolsList.push({
|
|
7128
|
+
id: externalTool.id,
|
|
7129
|
+
name: externalTool.name,
|
|
7130
|
+
params: toolInput,
|
|
7131
|
+
result: {
|
|
7132
|
+
_totalRecords: formattedForTracking.summary.totalRecords,
|
|
7133
|
+
_recordsShown: formattedForTracking.summary.recordsShown,
|
|
7134
|
+
_metadata: formattedForTracking.metadata,
|
|
7135
|
+
_sampleData: formattedForTracking.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS)
|
|
7136
|
+
},
|
|
7137
|
+
outputSchema: externalTool.outputSchema
|
|
7138
|
+
});
|
|
7139
|
+
}
|
|
7140
|
+
if (streamBuffer.hasCallback()) {
|
|
7141
|
+
streamBuffer.write(`\u2705 **${externalTool.name} completed successfully**
|
|
7142
|
+
|
|
7143
|
+
`);
|
|
7144
|
+
await streamDelay();
|
|
7145
|
+
}
|
|
7146
|
+
const formattedToolResult = formatToolResultForLLM(result, {
|
|
7147
|
+
toolName: externalTool.name,
|
|
7148
|
+
toolLimit: externalTool.limit,
|
|
7149
|
+
maxRows: DEFAULT_MAX_ROWS_FOR_LLM,
|
|
7150
|
+
maxCharsPerField: DEFAULT_MAX_CHARS_PER_FIELD2
|
|
7151
|
+
});
|
|
7152
|
+
if (formattedToolResult.truncationNote) {
|
|
7153
|
+
logger.info(`[${providerName}] Truncation: ${formattedToolResult.truncationNote}`);
|
|
7154
|
+
}
|
|
7155
|
+
return formatResultAsString(formattedToolResult);
|
|
7156
|
+
} catch (error) {
|
|
7157
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7158
|
+
logger.error(`[${providerName}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7159
|
+
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7160
|
+
if (streamBuffer.hasCallback()) {
|
|
7161
|
+
streamBuffer.write(`\u274C **${externalTool.name} failed:**
|
|
7162
|
+
\`\`\`
|
|
7163
|
+
${errorMsg}
|
|
7164
|
+
\`\`\`
|
|
7165
|
+
|
|
7166
|
+
`);
|
|
7167
|
+
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7168
|
+
streamBuffer.write(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7169
|
+
|
|
7170
|
+
`);
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
};
|
|
7177
|
+
|
|
7178
|
+
// src/userResponse/base-llm.ts
|
|
7179
|
+
var BaseLLM = class {
|
|
7180
|
+
constructor(config) {
|
|
7181
|
+
this.model = config?.model || this.getDefaultModel();
|
|
7182
|
+
this.fastModel = config?.fastModel || this.getDefaultFastModel();
|
|
7183
|
+
this.defaultLimit = config?.defaultLimit || 10;
|
|
7184
|
+
this.apiKey = config?.apiKey;
|
|
7185
|
+
this.modelStrategy = config?.modelStrategy || "fast";
|
|
7186
|
+
this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7187
|
+
this.queryService = new QueryExecutionService({
|
|
7188
|
+
defaultLimit: this.defaultLimit,
|
|
7189
|
+
getModelForTask: (taskType) => this.getModelForTask(taskType),
|
|
7190
|
+
getApiKey: (apiKey) => this.getApiKey(apiKey),
|
|
7191
|
+
providerName: this.getProviderName()
|
|
7192
|
+
});
|
|
7193
|
+
}
|
|
7194
|
+
/**
|
|
7195
|
+
* Get the appropriate model based on task type and model strategy
|
|
7196
|
+
* @param taskType - 'complex' for text generation/matching, 'simple' for classification/actions
|
|
7197
|
+
* @returns The model string to use for this task
|
|
7198
|
+
*/
|
|
7199
|
+
getModelForTask(taskType) {
|
|
7200
|
+
switch (this.modelStrategy) {
|
|
7201
|
+
case "best":
|
|
7202
|
+
return this.model;
|
|
7203
|
+
case "fast":
|
|
7204
|
+
return this.fastModel;
|
|
7205
|
+
case "balanced":
|
|
7206
|
+
default:
|
|
7207
|
+
return taskType === "complex" ? this.model : this.fastModel;
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
/**
|
|
7211
|
+
* Set the model strategy at runtime
|
|
7212
|
+
* @param strategy - 'best', 'fast', or 'balanced'
|
|
7213
|
+
*/
|
|
7214
|
+
setModelStrategy(strategy) {
|
|
7215
|
+
this.modelStrategy = strategy;
|
|
7216
|
+
logger.info(`[${this.getProviderName()}] Model strategy set to: ${strategy}`);
|
|
7217
|
+
}
|
|
7218
|
+
/**
|
|
7219
|
+
* Get the current model strategy
|
|
7220
|
+
* @returns The current model strategy
|
|
7221
|
+
*/
|
|
7222
|
+
getModelStrategy() {
|
|
7223
|
+
return this.modelStrategy;
|
|
7224
|
+
}
|
|
7225
|
+
/**
|
|
7226
|
+
* Set the conversation similarity threshold at runtime
|
|
7227
|
+
* @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
|
|
7228
|
+
*/
|
|
7229
|
+
setConversationSimilarityThreshold(threshold) {
|
|
7230
|
+
if (threshold < 0 || threshold > 1) {
|
|
7231
|
+
logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default ${DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD}`);
|
|
7232
|
+
this.conversationSimilarityThreshold = DEFAULT_CONVERSATION_SIMILARITY_THRESHOLD;
|
|
7233
|
+
return;
|
|
7234
|
+
}
|
|
7235
|
+
this.conversationSimilarityThreshold = threshold;
|
|
7236
|
+
}
|
|
7237
|
+
/**
|
|
7238
|
+
* Get the current conversation similarity threshold
|
|
7239
|
+
* @returns The current threshold value
|
|
7240
|
+
*/
|
|
7241
|
+
getConversationSimilarityThreshold() {
|
|
7242
|
+
return this.conversationSimilarityThreshold;
|
|
7243
|
+
}
|
|
7244
|
+
/**
|
|
7245
|
+
* Get the API key (from instance, parameter, or environment)
|
|
7246
|
+
*/
|
|
7247
|
+
getApiKey(apiKey) {
|
|
7248
|
+
return apiKey || this.apiKey || this.getDefaultApiKey();
|
|
7249
|
+
}
|
|
7250
|
+
/**
|
|
7251
|
+
* Check if a component contains a Form (data_modification component)
|
|
7252
|
+
* Forms have hardcoded defaultValues that become stale when cached
|
|
7253
|
+
* This checks both single Form components and Forms inside MultiComponentContainer
|
|
7254
|
+
*/
|
|
7255
|
+
containsFormComponent(component) {
|
|
7256
|
+
if (!component) return false;
|
|
7257
|
+
if (component.type === "Form" || component.name === "DynamicForm") {
|
|
7258
|
+
return true;
|
|
7259
|
+
}
|
|
7260
|
+
if (component.type === "Container" || component.name === "MultiComponentContainer") {
|
|
7261
|
+
const nestedComponents = component.props?.config?.components || [];
|
|
7262
|
+
for (const nested of nestedComponents) {
|
|
7263
|
+
if (nested.type === "Form" || nested.name === "DynamicForm") {
|
|
7264
|
+
return true;
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
return false;
|
|
7269
|
+
}
|
|
7270
|
+
/**
|
|
7271
|
+
* Match components from text response suggestions and generate follow-up questions
|
|
7272
|
+
* Takes a text response with component suggestions (c1:type format) and matches with available components
|
|
7273
|
+
* Also generates title, description, and intelligent follow-up questions (actions) based on the analysis
|
|
7274
|
+
* All components are placed in a default MultiComponentContainer layout
|
|
7275
|
+
* @param analysisContent - The text response containing component suggestions
|
|
7276
|
+
* @param components - List of available components
|
|
7277
|
+
* @param apiKey - Optional API key
|
|
7278
|
+
* @param componentStreamCallback - Optional callback to stream primary KPI component as soon as it's identified
|
|
7279
|
+
* @returns Object containing matched components, layout title/description, and follow-up actions
|
|
7280
|
+
*/
|
|
7281
|
+
async matchComponentsFromAnalysis(analysisContent, components, userPrompt, apiKey, componentStreamCallback, deferredTools, executedTools, collections, userId) {
|
|
7282
|
+
const methodStartTime = Date.now();
|
|
7283
|
+
const methodName = "matchComponentsFromAnalysis";
|
|
7284
|
+
logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")}`);
|
|
7285
|
+
try {
|
|
7286
|
+
let availableComponentsText = "No components available";
|
|
7287
|
+
if (components && components.length > 0) {
|
|
7288
|
+
availableComponentsText = components.map((comp, idx) => {
|
|
7289
|
+
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
7290
|
+
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
7291
|
+
return `${idx + 1}. ID: ${comp.id}
|
|
7292
|
+
Name: ${comp.name}
|
|
7293
|
+
Type: ${comp.type}
|
|
7294
|
+
Description: ${comp.description || "No description"}
|
|
7295
|
+
Keywords: ${keywords}
|
|
6172
7296
|
Props Structure: ${propsPreview}`;
|
|
6173
7297
|
}).join("\n\n");
|
|
6174
7298
|
}
|
|
6175
7299
|
let deferredToolsText = "No deferred external tools for this request.";
|
|
6176
7300
|
if (deferredTools && deferredTools.length > 0) {
|
|
6177
|
-
logger.info(`[${this.getProviderName()}] Passing ${deferredTools.length} deferred tools to component matching`);
|
|
6178
7301
|
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) => {
|
|
6179
7302
|
return `${idx + 1}. **${tool.name}**
|
|
6180
7303
|
toolId: "${tool.id}" (USE THIS EXACT VALUE - do not modify!)
|
|
@@ -6186,21 +7309,23 @@ Fixed SQL query:`;
|
|
|
6186
7309
|
}
|
|
6187
7310
|
let executedToolsText = "No external tools were executed for data fetching.";
|
|
6188
7311
|
if (executedTools && executedTools.length > 0) {
|
|
6189
|
-
|
|
6190
|
-
executedToolsText = "The following external tools were executed to fetch data.\n" + // '**IMPORTANT: For components displaying this data, use externalTool prop instead of query.**\n' +
|
|
6191
|
-
// '**IMPORTANT: Use ONLY the field names listed in outputSchema for config keys.**\n\n' +
|
|
6192
|
-
executedTools.map((tool, idx) => {
|
|
7312
|
+
executedToolsText = "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
|
|
6193
7313
|
let outputSchemaText = "Not available";
|
|
6194
7314
|
let fieldNamesList = "";
|
|
6195
|
-
|
|
7315
|
+
const recordCount = tool.result?._totalRecords ?? "unknown";
|
|
7316
|
+
let metadataText = "";
|
|
7317
|
+
if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
|
|
7318
|
+
const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
7319
|
+
metadataText = `
|
|
7320
|
+
\u{1F4CB} METADATA: ${metadataEntries}`;
|
|
7321
|
+
}
|
|
6196
7322
|
if (tool.outputSchema) {
|
|
6197
7323
|
const fields = tool.outputSchema.fields || [];
|
|
6198
|
-
recordCount = tool.result?._recordCount || (Array.isArray(tool.result) ? tool.result.length : "unknown");
|
|
6199
7324
|
const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
|
|
6200
7325
|
const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
|
|
6201
7326
|
fieldNamesList = `
|
|
6202
|
-
|
|
6203
|
-
|
|
7327
|
+
\u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
|
|
7328
|
+
\u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
|
|
6204
7329
|
const fieldsText = fields.map(
|
|
6205
7330
|
(f) => ` "${f.name}" (${f.type}): ${f.description}`
|
|
6206
7331
|
).join("\n");
|
|
@@ -6209,11 +7334,11 @@ Fixed SQL query:`;
|
|
|
6209
7334
|
${fieldsText}`;
|
|
6210
7335
|
}
|
|
6211
7336
|
return `${idx + 1}. **${tool.name}**
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
7337
|
+
toolId: "${tool.id}"
|
|
7338
|
+
toolName: "${tool.name}"
|
|
7339
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7340
|
+
recordCount: ${recordCount} rows returned${metadataText}
|
|
7341
|
+
outputSchema: ${outputSchemaText}${fieldNamesList}`;
|
|
6217
7342
|
}).join("\n\n");
|
|
6218
7343
|
}
|
|
6219
7344
|
const schemaDoc = schema.generateSchemaDocumentation();
|
|
@@ -6224,7 +7349,7 @@ ${fieldsText}`;
|
|
|
6224
7349
|
prompt: userPrompt || analysisContent,
|
|
6225
7350
|
collections,
|
|
6226
7351
|
userId,
|
|
6227
|
-
topK:
|
|
7352
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
6228
7353
|
});
|
|
6229
7354
|
knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
|
|
6230
7355
|
}
|
|
@@ -6239,30 +7364,12 @@ ${fieldsText}`;
|
|
|
6239
7364
|
KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext,
|
|
6240
7365
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6241
7366
|
});
|
|
6242
|
-
logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
|
|
6243
|
-
const extractPromptText = (content) => {
|
|
6244
|
-
if (typeof content === "string") return content;
|
|
6245
|
-
if (Array.isArray(content)) {
|
|
6246
|
-
return content.map((item) => {
|
|
6247
|
-
if (typeof item === "string") return item;
|
|
6248
|
-
if (item && typeof item.text === "string") return item.text;
|
|
6249
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
6250
|
-
return JSON.stringify(item, null, 2);
|
|
6251
|
-
}).join("\n\n---\n\n");
|
|
6252
|
-
}
|
|
6253
|
-
if (content && typeof content === "object") {
|
|
6254
|
-
if (typeof content.text === "string") return content.text;
|
|
6255
|
-
return JSON.stringify(content, null, 2);
|
|
6256
|
-
}
|
|
6257
|
-
return String(content);
|
|
6258
|
-
};
|
|
6259
7367
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "system", extractPromptText(prompts.system));
|
|
6260
7368
|
logger.logLLMPrompt("matchComponentsFromAnalysis", "user", `Text Analysis:
|
|
6261
7369
|
${analysisContent}
|
|
6262
7370
|
|
|
6263
7371
|
Executed Tools:
|
|
6264
7372
|
${executedToolsText}`);
|
|
6265
|
-
logCollector?.info("Matching components from text response...");
|
|
6266
7373
|
let fullResponseText = "";
|
|
6267
7374
|
let answerComponentExtracted = false;
|
|
6268
7375
|
const answerCallback = componentStreamCallback;
|
|
@@ -6322,52 +7429,44 @@ ${executedToolsText}`);
|
|
|
6322
7429
|
...answerComponentData.props
|
|
6323
7430
|
}
|
|
6324
7431
|
};
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
7432
|
+
let answerQuery = answerComponent.props?.query;
|
|
7433
|
+
if (answerQuery) {
|
|
7434
|
+
if (typeof answerQuery === "string") {
|
|
7435
|
+
answerQuery = ensureQueryLimit(answerQuery, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7436
|
+
} else if (answerQuery?.sql) {
|
|
7437
|
+
const queryObj = answerQuery;
|
|
7438
|
+
answerQuery = { ...queryObj, sql: ensureQueryLimit(queryObj.sql, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT) };
|
|
7439
|
+
}
|
|
7440
|
+
answerComponent.props.query = answerQuery;
|
|
6334
7441
|
}
|
|
6335
|
-
const answerQuery = answerComponent.props?.query;
|
|
6336
|
-
logger.info(`[${this.getProviderName()}] Answer component detected: ${answerComponent.name} (${answerComponent.type}), hasQuery: ${!!answerQuery}, hasDbExecute: ${!!collections?.["database"]?.["execute"]}`);
|
|
6337
7442
|
if (answerQuery && collections?.["database"]?.["execute"]) {
|
|
6338
7443
|
(async () => {
|
|
6339
|
-
const
|
|
7444
|
+
const maxRetries = MAX_QUERY_VALIDATION_RETRIES;
|
|
6340
7445
|
let attempts = 0;
|
|
6341
7446
|
let validated = false;
|
|
6342
7447
|
let currentQuery = answerQuery;
|
|
6343
7448
|
let currentQueryStr = typeof answerQuery === "string" ? answerQuery : answerQuery?.sql || "";
|
|
6344
7449
|
let lastError = "";
|
|
6345
|
-
|
|
6346
|
-
while (attempts < MAX_RETRIES && !validated) {
|
|
7450
|
+
while (attempts < maxRetries && !validated) {
|
|
6347
7451
|
attempts++;
|
|
6348
7452
|
try {
|
|
6349
|
-
const cacheKey = this.getQueryCacheKey(currentQuery);
|
|
7453
|
+
const cacheKey = this.queryService.getQueryCacheKey(currentQuery);
|
|
6350
7454
|
if (cacheKey) {
|
|
6351
|
-
logger.debug(`[${this.getProviderName()}] Answer component query validation attempt ${attempts}/${MAX_RETRIES}`);
|
|
6352
7455
|
const result2 = await collections["database"]["execute"]({ sql: cacheKey });
|
|
6353
7456
|
queryCache.set(cacheKey, result2);
|
|
6354
7457
|
validated = true;
|
|
6355
7458
|
if (currentQuery !== answerQuery) {
|
|
6356
7459
|
answerComponent.props.query = currentQuery;
|
|
6357
7460
|
}
|
|
6358
|
-
logger.info(`[${this.getProviderName()}] \u2713 Answer component query validated (attempt ${attempts}) - STREAMING TO FRONTEND NOW`);
|
|
6359
|
-
logCollector?.info(`\u2713 Answer component query validated - streaming to frontend`);
|
|
6360
|
-
logger.info(`[${this.getProviderName()}] Calling answerCallback for: ${answerComponent.name}`);
|
|
6361
7461
|
answerCallback(answerComponent);
|
|
6362
|
-
logger.info(`[${this.getProviderName()}] answerCallback completed for: ${answerComponent.name}`);
|
|
6363
7462
|
}
|
|
6364
7463
|
} catch (validationError) {
|
|
6365
7464
|
lastError = validationError instanceof Error ? validationError.message : String(validationError);
|
|
6366
|
-
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${
|
|
6367
|
-
if (attempts <
|
|
7465
|
+
logger.warn(`[${this.getProviderName()}] Answer component query validation failed (attempt ${attempts}/${maxRetries}): ${lastError}`);
|
|
7466
|
+
if (attempts < maxRetries) {
|
|
6368
7467
|
try {
|
|
6369
7468
|
logger.info(`[${this.getProviderName()}] Requesting LLM to fix answer component query...`);
|
|
6370
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
7469
|
+
const fixedQueryStr = await this.queryService.requestQueryFix(
|
|
6371
7470
|
currentQueryStr,
|
|
6372
7471
|
lastError,
|
|
6373
7472
|
{
|
|
@@ -6377,7 +7476,7 @@ ${executedToolsText}`);
|
|
|
6377
7476
|
},
|
|
6378
7477
|
apiKey
|
|
6379
7478
|
);
|
|
6380
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit,
|
|
7479
|
+
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
6381
7480
|
if (typeof currentQuery === "string") {
|
|
6382
7481
|
currentQuery = limitedFixedQuery;
|
|
6383
7482
|
} else {
|
|
@@ -6395,7 +7494,6 @@ ${executedToolsText}`);
|
|
|
6395
7494
|
}
|
|
6396
7495
|
if (!validated) {
|
|
6397
7496
|
logger.warn(`[${this.getProviderName()}] Answer component query validation failed after ${attempts} attempts - component will be excluded`);
|
|
6398
|
-
logCollector?.warn(`Answer component query validation failed: ${lastError} - component will be excluded from response`);
|
|
6399
7497
|
}
|
|
6400
7498
|
})();
|
|
6401
7499
|
} else {
|
|
@@ -6406,7 +7504,7 @@ ${executedToolsText}`);
|
|
|
6406
7504
|
}
|
|
6407
7505
|
}
|
|
6408
7506
|
} catch (e) {
|
|
6409
|
-
logger.
|
|
7507
|
+
logger.error(`[${this.getProviderName()}] Partial answerComponent parse failed, waiting for more data...`);
|
|
6410
7508
|
}
|
|
6411
7509
|
}
|
|
6412
7510
|
}
|
|
@@ -6418,7 +7516,7 @@ ${executedToolsText}`);
|
|
|
6418
7516
|
},
|
|
6419
7517
|
{
|
|
6420
7518
|
model: this.getModelForTask("complex"),
|
|
6421
|
-
maxTokens:
|
|
7519
|
+
maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
|
|
6422
7520
|
temperature: 0,
|
|
6423
7521
|
apiKey: this.getApiKey(apiKey),
|
|
6424
7522
|
partial: partialCallback
|
|
@@ -6436,155 +7534,20 @@ ${executedToolsText}`);
|
|
|
6436
7534
|
logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
|
|
6437
7535
|
const rawActions = result.actions || [];
|
|
6438
7536
|
const actions = convertQuestionsToActions(rawActions);
|
|
6439
|
-
if (matchedComponents.length > 0) {
|
|
6440
|
-
matchedComponents.forEach((comp, idx) => {
|
|
6441
|
-
logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
|
|
6442
|
-
if (comp.props?.query) {
|
|
6443
|
-
logCollector?.logQuery(
|
|
6444
|
-
`Component ${idx + 1} query`,
|
|
6445
|
-
comp.props.query,
|
|
6446
|
-
{ componentName: comp.componentName, title: comp.props.title }
|
|
6447
|
-
);
|
|
6448
|
-
}
|
|
6449
|
-
});
|
|
6450
|
-
}
|
|
6451
7537
|
const finalComponents = matchedComponents.map((mc) => {
|
|
6452
7538
|
const originalComponent = components.find((c) => c.id === mc.componentId);
|
|
6453
7539
|
if (!originalComponent) {
|
|
6454
|
-
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
6455
|
-
return null;
|
|
6456
|
-
}
|
|
6457
|
-
let cleanedProps = { ...mc.props };
|
|
6458
|
-
if (cleanedProps.externalTool) {
|
|
6459
|
-
const toolId = cleanedProps.externalTool.toolId;
|
|
6460
|
-
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
6461
|
-
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
6462
|
-
if (!isValidTool) {
|
|
6463
|
-
logger.warn(`[${this.getProviderName()}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
6464
|
-
cleanedProps.externalTool = null;
|
|
6465
|
-
} else {
|
|
6466
|
-
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
6467
|
-
if (executedTool?.outputSchema?.fields && cleanedProps.config) {
|
|
6468
|
-
const validFieldNames = executedTool.outputSchema.fields.map((f) => f.name);
|
|
6469
|
-
const validFieldNamesLower = validFieldNames.map((n) => n.toLowerCase());
|
|
6470
|
-
const findMatchingField = (fieldName, configKey) => {
|
|
6471
|
-
if (!fieldName) return null;
|
|
6472
|
-
const lowerField = fieldName.toLowerCase();
|
|
6473
|
-
const exactIdx = validFieldNamesLower.indexOf(lowerField);
|
|
6474
|
-
if (exactIdx !== -1) return validFieldNames[exactIdx];
|
|
6475
|
-
const containsMatches = validFieldNames.filter(
|
|
6476
|
-
(_, i) => validFieldNamesLower[i].includes(lowerField) || lowerField.includes(validFieldNamesLower[i])
|
|
6477
|
-
);
|
|
6478
|
-
if (containsMatches.length === 1) return containsMatches[0];
|
|
6479
|
-
const fieldTypes = executedTool.outputSchema.fields.reduce((acc, f) => {
|
|
6480
|
-
acc[f.name] = f.type;
|
|
6481
|
-
return acc;
|
|
6482
|
-
}, {});
|
|
6483
|
-
const numericConfigKeys = ["yAxisKey", "valueKey", "aggregationField", "sizeKey"];
|
|
6484
|
-
const stringConfigKeys = ["xAxisKey", "nameKey", "labelKey", "groupBy"];
|
|
6485
|
-
if (numericConfigKeys.includes(configKey)) {
|
|
6486
|
-
const numericFields = validFieldNames.filter((f) => fieldTypes[f] === "number");
|
|
6487
|
-
const match = numericFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
|
|
6488
|
-
if (match) return match;
|
|
6489
|
-
if (numericFields.length > 0) {
|
|
6490
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first numeric field: ${numericFields[0]}`);
|
|
6491
|
-
return numericFields[0];
|
|
6492
|
-
}
|
|
6493
|
-
}
|
|
6494
|
-
if (stringConfigKeys.includes(configKey)) {
|
|
6495
|
-
const stringFields = validFieldNames.filter((f) => fieldTypes[f] === "string");
|
|
6496
|
-
const match = stringFields.find((f) => f.toLowerCase().includes(lowerField) || lowerField.includes(f.toLowerCase()));
|
|
6497
|
-
if (match) return match;
|
|
6498
|
-
if (stringFields.length > 0) {
|
|
6499
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first string field: ${stringFields[0]}`);
|
|
6500
|
-
return stringFields[0];
|
|
6501
|
-
}
|
|
6502
|
-
}
|
|
6503
|
-
logger.warn(`[${this.getProviderName()}] No match for "${fieldName}", using first field: ${validFieldNames[0]}`);
|
|
6504
|
-
return validFieldNames[0];
|
|
6505
|
-
};
|
|
6506
|
-
const configFieldsToValidate = [
|
|
6507
|
-
"xAxisKey",
|
|
6508
|
-
"yAxisKey",
|
|
6509
|
-
"valueKey",
|
|
6510
|
-
"nameKey",
|
|
6511
|
-
"labelKey",
|
|
6512
|
-
"groupBy",
|
|
6513
|
-
"aggregationField",
|
|
6514
|
-
"seriesKey",
|
|
6515
|
-
"sizeKey",
|
|
6516
|
-
"xAggregationField",
|
|
6517
|
-
"yAggregationField"
|
|
6518
|
-
];
|
|
6519
|
-
for (const configKey of configFieldsToValidate) {
|
|
6520
|
-
const fieldValue = cleanedProps.config[configKey];
|
|
6521
|
-
if (fieldValue && typeof fieldValue === "string") {
|
|
6522
|
-
if (!validFieldNames.includes(fieldValue)) {
|
|
6523
|
-
const correctedField = findMatchingField(fieldValue, configKey);
|
|
6524
|
-
if (correctedField) {
|
|
6525
|
-
logger.warn(`[${this.getProviderName()}] Correcting config.${configKey}: "${fieldValue}" \u2192 "${correctedField}"`);
|
|
6526
|
-
cleanedProps.config[configKey] = correctedField;
|
|
6527
|
-
}
|
|
6528
|
-
}
|
|
6529
|
-
}
|
|
6530
|
-
}
|
|
6531
|
-
if (Array.isArray(cleanedProps.config.series)) {
|
|
6532
|
-
cleanedProps.config.series = cleanedProps.config.series.map((s) => {
|
|
6533
|
-
if (s.dataKey && typeof s.dataKey === "string" && !validFieldNames.includes(s.dataKey)) {
|
|
6534
|
-
const correctedField = findMatchingField(s.dataKey, "yAxisKey");
|
|
6535
|
-
if (correctedField) {
|
|
6536
|
-
logger.warn(`[${this.getProviderName()}] Correcting series.dataKey: "${s.dataKey}" \u2192 "${correctedField}"`);
|
|
6537
|
-
return { ...s, dataKey: correctedField };
|
|
6538
|
-
}
|
|
6539
|
-
}
|
|
6540
|
-
return s;
|
|
6541
|
-
});
|
|
6542
|
-
}
|
|
6543
|
-
}
|
|
6544
|
-
}
|
|
6545
|
-
}
|
|
6546
|
-
if (cleanedProps.query) {
|
|
6547
|
-
const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
|
|
6548
|
-
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
6549
|
-
logger.warn(`[${this.getProviderName()}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
6550
|
-
cleanedProps.query = null;
|
|
6551
|
-
}
|
|
6552
|
-
}
|
|
6553
|
-
if (cleanedProps.query) {
|
|
6554
|
-
const queryStr = typeof cleanedProps.query === "string" ? cleanedProps.query : cleanedProps.query?.sql || "";
|
|
6555
|
-
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
6556
|
-
if (fixed) {
|
|
6557
|
-
logger.warn(`[${this.getProviderName()}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
6558
|
-
if (typeof cleanedProps.query === "string") {
|
|
6559
|
-
cleanedProps.query = fixedQuery;
|
|
6560
|
-
} else if (cleanedProps.query?.sql) {
|
|
6561
|
-
cleanedProps.query.sql = fixedQuery;
|
|
6562
|
-
}
|
|
6563
|
-
}
|
|
6564
|
-
}
|
|
6565
|
-
if (cleanedProps.query) {
|
|
6566
|
-
if (typeof cleanedProps.query === "string") {
|
|
6567
|
-
cleanedProps.query = ensureQueryLimit(
|
|
6568
|
-
cleanedProps.query,
|
|
6569
|
-
this.defaultLimit,
|
|
6570
|
-
10
|
|
6571
|
-
// maxLimit - enforce maximum of 10 rows for component queries
|
|
6572
|
-
);
|
|
6573
|
-
} else if (cleanedProps.query?.sql) {
|
|
6574
|
-
cleanedProps.query = {
|
|
6575
|
-
...cleanedProps.query,
|
|
6576
|
-
sql: ensureQueryLimit(
|
|
6577
|
-
cleanedProps.query.sql,
|
|
6578
|
-
this.defaultLimit,
|
|
6579
|
-
10
|
|
6580
|
-
// maxLimit - enforce maximum of 10 rows for component queries
|
|
6581
|
-
)
|
|
6582
|
-
};
|
|
6583
|
-
}
|
|
6584
|
-
}
|
|
6585
|
-
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
6586
|
-
logger.info(`[${this.getProviderName()}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
7540
|
+
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
7541
|
+
return null;
|
|
6587
7542
|
}
|
|
7543
|
+
const cleanedProps = processComponentProps(
|
|
7544
|
+
mc.props,
|
|
7545
|
+
executedTools,
|
|
7546
|
+
{
|
|
7547
|
+
providerName: this.getProviderName(),
|
|
7548
|
+
defaultLimit: this.defaultLimit
|
|
7549
|
+
}
|
|
7550
|
+
);
|
|
6588
7551
|
return {
|
|
6589
7552
|
...originalComponent,
|
|
6590
7553
|
props: {
|
|
@@ -6595,27 +7558,22 @@ ${executedToolsText}`);
|
|
|
6595
7558
|
}).filter(Boolean);
|
|
6596
7559
|
let validatedComponents = finalComponents;
|
|
6597
7560
|
if (collections?.["database"]?.["execute"]) {
|
|
6598
|
-
logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
|
|
6599
|
-
logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
|
|
6600
7561
|
try {
|
|
6601
|
-
const validationResult = await this.
|
|
7562
|
+
const validationResult = await this.queryService.validateComponentQueries(
|
|
6602
7563
|
finalComponents,
|
|
6603
7564
|
collections,
|
|
6604
|
-
apiKey
|
|
6605
|
-
logCollector
|
|
7565
|
+
apiKey
|
|
6606
7566
|
);
|
|
6607
7567
|
validatedComponents = validationResult.components;
|
|
6608
7568
|
const queriedComponents = finalComponents.filter((c) => c.props?.query);
|
|
6609
7569
|
const validatedQueries = validatedComponents.filter((c) => c.props?.query);
|
|
6610
7570
|
logger.info(`[${this.getProviderName()}] Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
|
|
6611
|
-
logCollector?.info(`Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
|
|
6612
7571
|
} catch (validationError) {
|
|
6613
7572
|
const validationErrorMsg = validationError instanceof Error ? validationError.message : String(validationError);
|
|
6614
7573
|
logger.error(`[${this.getProviderName()}] Query validation error: ${validationErrorMsg}`);
|
|
6615
|
-
logCollector?.error(`Query validation error: ${validationErrorMsg}`);
|
|
6616
7574
|
}
|
|
6617
7575
|
} else {
|
|
6618
|
-
logger.
|
|
7576
|
+
logger.error(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
|
|
6619
7577
|
}
|
|
6620
7578
|
const methodDuration = Date.now() - methodStartTime;
|
|
6621
7579
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${validatedComponents.length} | actions: ${actions.length}`);
|
|
@@ -6629,7 +7587,6 @@ ${executedToolsText}`);
|
|
|
6629
7587
|
const methodDuration = Date.now() - methodStartTime;
|
|
6630
7588
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6631
7589
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
6632
|
-
logCollector?.error(`Failed to match components: ${errorMsg}`);
|
|
6633
7590
|
return {
|
|
6634
7591
|
components: [],
|
|
6635
7592
|
layoutTitle: "Dashboard",
|
|
@@ -6638,158 +7595,11 @@ ${executedToolsText}`);
|
|
|
6638
7595
|
};
|
|
6639
7596
|
}
|
|
6640
7597
|
}
|
|
6641
|
-
/**
|
|
6642
|
-
* Validate a single component's query with retry logic
|
|
6643
|
-
* @param component - The component to validate
|
|
6644
|
-
* @param collections - Collections object containing database execute function
|
|
6645
|
-
* @param apiKey - Optional API key for LLM calls
|
|
6646
|
-
* @param logCollector - Optional log collector for logging
|
|
6647
|
-
* @returns Object with validated component (or null if failed) and query result
|
|
6648
|
-
*/
|
|
6649
|
-
async validateSingleComponentQuery(component, collections, apiKey, logCollector) {
|
|
6650
|
-
const MAX_RETRIES = 3;
|
|
6651
|
-
const query = component.props?.query;
|
|
6652
|
-
const originalQueryKey = this.getQueryCacheKey(query);
|
|
6653
|
-
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
6654
|
-
let finalQueryKey = originalQueryKey;
|
|
6655
|
-
let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
|
|
6656
|
-
let currentQueryStr = queryStr;
|
|
6657
|
-
let validated = false;
|
|
6658
|
-
let lastError = "";
|
|
6659
|
-
let result = null;
|
|
6660
|
-
let attempts = 0;
|
|
6661
|
-
logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
|
|
6662
|
-
while (attempts < MAX_RETRIES && !validated) {
|
|
6663
|
-
attempts++;
|
|
6664
|
-
try {
|
|
6665
|
-
logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
|
|
6666
|
-
const validationResult = await this.executeQueryForValidation(currentQuery, collections);
|
|
6667
|
-
result = validationResult.result;
|
|
6668
|
-
validated = true;
|
|
6669
|
-
queryCache.set(validationResult.cacheKey, result);
|
|
6670
|
-
logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
|
|
6671
|
-
logCollector?.info(`\u2713 Query validated for ${component.name}`);
|
|
6672
|
-
if (currentQueryStr !== queryStr) {
|
|
6673
|
-
const fixedQuery = typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr };
|
|
6674
|
-
component.props = {
|
|
6675
|
-
...component.props,
|
|
6676
|
-
query: fixedQuery
|
|
6677
|
-
};
|
|
6678
|
-
finalQueryKey = this.getQueryCacheKey(fixedQuery);
|
|
6679
|
-
logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
|
|
6680
|
-
}
|
|
6681
|
-
} catch (error) {
|
|
6682
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
6683
|
-
logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
|
|
6684
|
-
logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
|
|
6685
|
-
if (attempts >= MAX_RETRIES) {
|
|
6686
|
-
logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, excluding from response`);
|
|
6687
|
-
logCollector?.error(`Max retries reached for ${component.name}, component excluded from response`);
|
|
6688
|
-
break;
|
|
6689
|
-
}
|
|
6690
|
-
logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
|
|
6691
|
-
logCollector?.info(`Requesting query fix for ${component.name}...`);
|
|
6692
|
-
try {
|
|
6693
|
-
const fixedQueryStr = await this.requestQueryFix(
|
|
6694
|
-
currentQueryStr,
|
|
6695
|
-
lastError,
|
|
6696
|
-
{
|
|
6697
|
-
name: component.name,
|
|
6698
|
-
type: component.type,
|
|
6699
|
-
title: component.props?.title
|
|
6700
|
-
},
|
|
6701
|
-
apiKey
|
|
6702
|
-
);
|
|
6703
|
-
if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
|
|
6704
|
-
logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
|
|
6705
|
-
const limitedFixedQuery = ensureQueryLimit(fixedQueryStr, this.defaultLimit, 10);
|
|
6706
|
-
currentQueryStr = limitedFixedQuery;
|
|
6707
|
-
if (typeof currentQuery === "string") {
|
|
6708
|
-
currentQuery = limitedFixedQuery;
|
|
6709
|
-
} else {
|
|
6710
|
-
currentQuery = { ...currentQuery, sql: limitedFixedQuery };
|
|
6711
|
-
}
|
|
6712
|
-
} else {
|
|
6713
|
-
logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
|
|
6714
|
-
break;
|
|
6715
|
-
}
|
|
6716
|
-
} catch (fixError) {
|
|
6717
|
-
const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
6718
|
-
logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
|
|
6719
|
-
break;
|
|
6720
|
-
}
|
|
6721
|
-
}
|
|
6722
|
-
}
|
|
6723
|
-
if (!validated) {
|
|
6724
|
-
logger.warn(`[${this.getProviderName()}] Component ${component.name} excluded from response due to failed query validation`);
|
|
6725
|
-
logCollector?.warn(`Component ${component.name} excluded from response`);
|
|
6726
|
-
}
|
|
6727
|
-
return {
|
|
6728
|
-
component: validated ? component : null,
|
|
6729
|
-
queryKey: finalQueryKey,
|
|
6730
|
-
result,
|
|
6731
|
-
validated
|
|
6732
|
-
};
|
|
6733
|
-
}
|
|
6734
|
-
/**
|
|
6735
|
-
* Validate component queries against the database and retry with LLM fixes if they fail
|
|
6736
|
-
* Uses parallel execution for faster validation
|
|
6737
|
-
* @param components - Array of components with potential queries
|
|
6738
|
-
* @param collections - Collections object containing database execute function
|
|
6739
|
-
* @param apiKey - Optional API key for LLM calls
|
|
6740
|
-
* @param logCollector - Optional log collector for logging
|
|
6741
|
-
* @returns Object with validated components and a map of query results
|
|
6742
|
-
*/
|
|
6743
|
-
async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
|
|
6744
|
-
const queryResults = /* @__PURE__ */ new Map();
|
|
6745
|
-
const validatedComponents = [];
|
|
6746
|
-
const componentsWithoutQuery = [];
|
|
6747
|
-
const componentsWithQuery = [];
|
|
6748
|
-
for (const component of components) {
|
|
6749
|
-
if (!component.props?.query) {
|
|
6750
|
-
componentsWithoutQuery.push(component);
|
|
6751
|
-
} else {
|
|
6752
|
-
componentsWithQuery.push(component);
|
|
6753
|
-
}
|
|
6754
|
-
}
|
|
6755
|
-
validatedComponents.push(...componentsWithoutQuery);
|
|
6756
|
-
if (componentsWithQuery.length === 0) {
|
|
6757
|
-
return { components: validatedComponents, queryResults };
|
|
6758
|
-
}
|
|
6759
|
-
logger.info(`[${this.getProviderName()}] Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6760
|
-
logCollector?.info(`Validating ${componentsWithQuery.length} component queries in parallel...`);
|
|
6761
|
-
const validationPromises = componentsWithQuery.map(
|
|
6762
|
-
(component) => this.validateSingleComponentQuery(component, collections, apiKey, logCollector)
|
|
6763
|
-
);
|
|
6764
|
-
const results = await Promise.allSettled(validationPromises);
|
|
6765
|
-
for (let i = 0; i < results.length; i++) {
|
|
6766
|
-
const result = results[i];
|
|
6767
|
-
const component = componentsWithQuery[i];
|
|
6768
|
-
if (result.status === "fulfilled") {
|
|
6769
|
-
const { component: validatedComponent, queryKey, result: queryResult, validated } = result.value;
|
|
6770
|
-
if (validated && validatedComponent) {
|
|
6771
|
-
validatedComponents.push(validatedComponent);
|
|
6772
|
-
if (queryResult) {
|
|
6773
|
-
queryResults.set(queryKey, queryResult);
|
|
6774
|
-
queryResults.set(`${component.id}:${queryKey}`, queryResult);
|
|
6775
|
-
}
|
|
6776
|
-
}
|
|
6777
|
-
} else {
|
|
6778
|
-
logger.error(`[${this.getProviderName()}] Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6779
|
-
logCollector?.error(`Unexpected error validating ${component.name}: ${result.reason}`);
|
|
6780
|
-
}
|
|
6781
|
-
}
|
|
6782
|
-
logger.info(`[${this.getProviderName()}] Parallel validation complete: ${validatedComponents.length}/${components.length} components validated`);
|
|
6783
|
-
return {
|
|
6784
|
-
components: validatedComponents,
|
|
6785
|
-
queryResults
|
|
6786
|
-
};
|
|
6787
|
-
}
|
|
6788
7598
|
/**
|
|
6789
7599
|
* Classify user question into category and detect external tools needed
|
|
6790
7600
|
* Determines if question is for data analysis, requires external tools, or needs text response
|
|
6791
7601
|
*/
|
|
6792
|
-
async classifyQuestionCategory(userPrompt, apiKey,
|
|
7602
|
+
async classifyQuestionCategory(userPrompt, apiKey, conversationHistory, externalTools) {
|
|
6793
7603
|
const methodStartTime = Date.now();
|
|
6794
7604
|
const methodName = "classifyQuestionCategory";
|
|
6795
7605
|
const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
|
|
@@ -6809,24 +7619,8 @@ ${executedToolsText}`);
|
|
|
6809
7619
|
SCHEMA_DOC: schemaDoc || "No database schema available",
|
|
6810
7620
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
6811
7621
|
});
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
if (Array.isArray(content)) {
|
|
6815
|
-
return content.map((item) => {
|
|
6816
|
-
if (typeof item === "string") return item;
|
|
6817
|
-
if (item && typeof item.text === "string") return item.text;
|
|
6818
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
6819
|
-
return JSON.stringify(item, null, 2);
|
|
6820
|
-
}).join("\n\n---\n\n");
|
|
6821
|
-
}
|
|
6822
|
-
if (content && typeof content === "object") {
|
|
6823
|
-
if (typeof content.text === "string") return content.text;
|
|
6824
|
-
return JSON.stringify(content, null, 2);
|
|
6825
|
-
}
|
|
6826
|
-
return String(content);
|
|
6827
|
-
};
|
|
6828
|
-
logger.logLLMPrompt("classifyQuestionCategory", "system", extractTextContent(prompts.system));
|
|
6829
|
-
logger.logLLMPrompt("classifyQuestionCategory", "user", extractTextContent(prompts.user));
|
|
7622
|
+
logger.logLLMPrompt("classifyQuestionCategory", "system", extractPromptText(prompts.system));
|
|
7623
|
+
logger.logLLMPrompt("classifyQuestionCategory", "user", extractPromptText(prompts.user));
|
|
6830
7624
|
const result = await LLM.stream(
|
|
6831
7625
|
{
|
|
6832
7626
|
sys: prompts.system,
|
|
@@ -6834,23 +7628,13 @@ ${executedToolsText}`);
|
|
|
6834
7628
|
},
|
|
6835
7629
|
{
|
|
6836
7630
|
model: this.getModelForTask("simple"),
|
|
6837
|
-
maxTokens:
|
|
7631
|
+
maxTokens: MAX_TOKENS_CLASSIFICATION,
|
|
6838
7632
|
temperature: 0,
|
|
6839
7633
|
apiKey: this.getApiKey(apiKey)
|
|
6840
7634
|
},
|
|
6841
7635
|
true
|
|
6842
7636
|
// Parse as JSON
|
|
6843
7637
|
);
|
|
6844
|
-
logCollector?.logExplanation(
|
|
6845
|
-
"Question category classified",
|
|
6846
|
-
result.reasoning || "No reasoning provided",
|
|
6847
|
-
{
|
|
6848
|
-
category: result.category,
|
|
6849
|
-
externalTools: result.externalTools || [],
|
|
6850
|
-
dataAnalysisType: result.dataAnalysisType,
|
|
6851
|
-
confidence: result.confidence
|
|
6852
|
-
}
|
|
6853
|
-
);
|
|
6854
7638
|
const methodDuration = Date.now() - methodStartTime;
|
|
6855
7639
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | category: ${result.category} | confidence: ${result.confidence}% | tools: ${(result.externalTools || []).length}`);
|
|
6856
7640
|
return {
|
|
@@ -6864,7 +7648,6 @@ ${executedToolsText}`);
|
|
|
6864
7648
|
const methodDuration = Date.now() - methodStartTime;
|
|
6865
7649
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6866
7650
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
6867
|
-
logger.debug(`[${this.getProviderName()}] Category classification error details:`, error);
|
|
6868
7651
|
throw error;
|
|
6869
7652
|
}
|
|
6870
7653
|
}
|
|
@@ -6873,7 +7656,7 @@ ${executedToolsText}`);
|
|
|
6873
7656
|
* Takes a matched UI block from semantic search and modifies its props to answer the new question
|
|
6874
7657
|
* Also adapts the cached text response to match the new question
|
|
6875
7658
|
*/
|
|
6876
|
-
async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey,
|
|
7659
|
+
async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, cachedTextResponse) {
|
|
6877
7660
|
const methodStartTime = Date.now();
|
|
6878
7661
|
const methodName = "adaptUIBlockParameters";
|
|
6879
7662
|
const promptPreview = currentUserPrompt.substring(0, 50) + (currentUserPrompt.length > 50 ? "..." : "");
|
|
@@ -6907,7 +7690,7 @@ ${executedToolsText}`);
|
|
|
6907
7690
|
},
|
|
6908
7691
|
{
|
|
6909
7692
|
model: this.getModelForTask("complex"),
|
|
6910
|
-
maxTokens:
|
|
7693
|
+
maxTokens: MAX_TOKENS_ADAPTATION,
|
|
6911
7694
|
temperature: 0,
|
|
6912
7695
|
apiKey: this.getApiKey(apiKey)
|
|
6913
7696
|
},
|
|
@@ -6917,11 +7700,6 @@ ${executedToolsText}`);
|
|
|
6917
7700
|
if (!result.success) {
|
|
6918
7701
|
const methodDuration2 = Date.now() - methodStartTime;
|
|
6919
7702
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: adaptation failed - ${result.reason}`);
|
|
6920
|
-
logCollector?.warn(
|
|
6921
|
-
"Could not adapt matched UI block",
|
|
6922
|
-
"explanation",
|
|
6923
|
-
{ reason: result.reason }
|
|
6924
|
-
);
|
|
6925
7703
|
return {
|
|
6926
7704
|
success: false,
|
|
6927
7705
|
explanation: result.explanation || "Adaptation not possible"
|
|
@@ -6933,14 +7711,6 @@ ${executedToolsText}`);
|
|
|
6933
7711
|
this.defaultLimit
|
|
6934
7712
|
);
|
|
6935
7713
|
}
|
|
6936
|
-
logCollector?.logExplanation(
|
|
6937
|
-
"UI block parameters adapted",
|
|
6938
|
-
result.explanation || "Parameters adapted successfully",
|
|
6939
|
-
{
|
|
6940
|
-
parametersChanged: result.parametersChanged || [],
|
|
6941
|
-
componentType: result.adaptedComponent?.type
|
|
6942
|
-
}
|
|
6943
|
-
);
|
|
6944
7714
|
const methodDuration = Date.now() - methodStartTime;
|
|
6945
7715
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | result: success | changes: ${(result.parametersChanged || []).length}`);
|
|
6946
7716
|
return {
|
|
@@ -6954,7 +7724,6 @@ ${executedToolsText}`);
|
|
|
6954
7724
|
const methodDuration = Date.now() - methodStartTime;
|
|
6955
7725
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6956
7726
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
6957
|
-
logger.debug(`[${this.getProviderName()}] Adaptation error details:`, error);
|
|
6958
7727
|
return {
|
|
6959
7728
|
success: false,
|
|
6960
7729
|
explanation: `Error adapting parameters: ${errorMsg}`
|
|
@@ -6967,14 +7736,12 @@ ${executedToolsText}`);
|
|
|
6967
7736
|
* Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
|
|
6968
7737
|
* After generating text response, if components are provided, matches suggested components
|
|
6969
7738
|
*/
|
|
6970
|
-
async generateTextResponse(userPrompt, apiKey,
|
|
7739
|
+
async generateTextResponse(userPrompt, apiKey, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
|
|
6971
7740
|
const methodStartTime = Date.now();
|
|
6972
7741
|
const methodName = "generateTextResponse";
|
|
6973
7742
|
const promptPreview = userPrompt.substring(0, 50) + (userPrompt.length > 50 ? "..." : "");
|
|
6974
7743
|
logger.info(`[${this.getProviderName()}] [TIMING] START ${methodName} | model: ${this.getModelForTask("complex")} | category: ${category} | prompt: "${promptPreview}"`);
|
|
6975
7744
|
const errors = [];
|
|
6976
|
-
logger.debug(`[${this.getProviderName()}] Starting text response generation`);
|
|
6977
|
-
logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
|
|
6978
7745
|
try {
|
|
6979
7746
|
let availableToolsDoc = "No external tools are available for this request.";
|
|
6980
7747
|
if (externalTools && externalTools.length > 0) {
|
|
@@ -7024,7 +7791,7 @@ ${executedToolsText}`);
|
|
|
7024
7791
|
prompt: userPrompt,
|
|
7025
7792
|
collections,
|
|
7026
7793
|
userId,
|
|
7027
|
-
topK:
|
|
7794
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
7028
7795
|
});
|
|
7029
7796
|
const knowledgeBaseContext = kbResult.combinedContext;
|
|
7030
7797
|
const prompts = await promptLoader.loadPrompts("text-response", {
|
|
@@ -7036,27 +7803,8 @@ ${executedToolsText}`);
|
|
|
7036
7803
|
AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc,
|
|
7037
7804
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
7038
7805
|
});
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
if (Array.isArray(content)) {
|
|
7042
|
-
return content.map((item) => {
|
|
7043
|
-
if (typeof item === "string") return item;
|
|
7044
|
-
if (item && typeof item.text === "string") return item.text;
|
|
7045
|
-
if (item && item.content && typeof item.content === "string") return item.content;
|
|
7046
|
-
return JSON.stringify(item, null, 2);
|
|
7047
|
-
}).join("\n\n---\n\n");
|
|
7048
|
-
}
|
|
7049
|
-
if (content && typeof content === "object") {
|
|
7050
|
-
if (typeof content.text === "string") return content.text;
|
|
7051
|
-
return JSON.stringify(content, null, 2);
|
|
7052
|
-
}
|
|
7053
|
-
return String(content);
|
|
7054
|
-
};
|
|
7055
|
-
logger.logLLMPrompt("generateTextResponse", "system", extractText(prompts.system));
|
|
7056
|
-
logger.logLLMPrompt("generateTextResponse", "user", extractText(prompts.user));
|
|
7057
|
-
logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
|
|
7058
|
-
logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
|
|
7059
|
-
logCollector?.info("Generating text response with query execution capability...");
|
|
7806
|
+
logger.logLLMPrompt("generateTextResponse", "system", extractPromptText(prompts.system));
|
|
7807
|
+
logger.logLLMPrompt("generateTextResponse", "user", extractPromptText(prompts.user));
|
|
7060
7808
|
const tools = [{
|
|
7061
7809
|
name: "execute_query",
|
|
7062
7810
|
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.",
|
|
@@ -7085,7 +7833,6 @@ ${executedToolsText}`);
|
|
|
7085
7833
|
const executableTools = externalTools.filter(
|
|
7086
7834
|
(t) => t.executionType === "immediate" || t.executionType === "deferred" && t.userProvidedData
|
|
7087
7835
|
);
|
|
7088
|
-
logger.info(`[${this.getProviderName()}] Executable tools: ${executableTools.length} of ${externalTools.length} total`);
|
|
7089
7836
|
const addedToolIds = /* @__PURE__ */ new Set();
|
|
7090
7837
|
executableTools.forEach((tool) => {
|
|
7091
7838
|
if (addedToolIds.has(tool.id)) {
|
|
@@ -7093,7 +7840,6 @@ ${executedToolsText}`);
|
|
|
7093
7840
|
return;
|
|
7094
7841
|
}
|
|
7095
7842
|
addedToolIds.add(tool.id);
|
|
7096
|
-
logger.info(`[${this.getProviderName()}] Processing executable tool:`, JSON.stringify(tool, null, 2));
|
|
7097
7843
|
const properties = {};
|
|
7098
7844
|
const required = [];
|
|
7099
7845
|
Object.entries(tool.params || {}).forEach(([key, typeOrValue]) => {
|
|
@@ -7162,327 +7908,26 @@ ${executedToolsText}`);
|
|
|
7162
7908
|
input_schema: inputSchema
|
|
7163
7909
|
});
|
|
7164
7910
|
});
|
|
7165
|
-
logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
|
|
7166
|
-
logger.
|
|
7167
|
-
}
|
|
7168
|
-
const
|
|
7169
|
-
const
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
const wrappedStreamCallback = streamCallback ? (chunk) => {
|
|
7186
|
-
fullStreamedText += chunk;
|
|
7187
|
-
streamBuffer += chunk;
|
|
7188
|
-
if (chunk.includes("\n") || chunk.length > 100) {
|
|
7189
|
-
if (flushTimer) {
|
|
7190
|
-
clearTimeout(flushTimer);
|
|
7191
|
-
flushTimer = null;
|
|
7192
|
-
}
|
|
7193
|
-
flushStreamBuffer();
|
|
7194
|
-
} else if (!flushTimer) {
|
|
7195
|
-
flushTimer = setTimeout(flushStreamBuffer, FLUSH_INTERVAL);
|
|
7196
|
-
}
|
|
7197
|
-
} : void 0;
|
|
7198
|
-
const flushStream = () => {
|
|
7199
|
-
if (flushTimer) {
|
|
7200
|
-
clearTimeout(flushTimer);
|
|
7201
|
-
flushTimer = null;
|
|
7202
|
-
}
|
|
7203
|
-
flushStreamBuffer();
|
|
7204
|
-
};
|
|
7205
|
-
const streamDelay = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7206
|
-
const withProgressHeartbeat = async (operation, progressMessage, intervalMs = 1e3) => {
|
|
7207
|
-
if (!wrappedStreamCallback) {
|
|
7208
|
-
return operation();
|
|
7209
|
-
}
|
|
7210
|
-
const startTime = Date.now();
|
|
7211
|
-
await streamDelay(30);
|
|
7212
|
-
wrappedStreamCallback(`\u23F3 ${progressMessage}`);
|
|
7213
|
-
const heartbeatInterval = setInterval(() => {
|
|
7214
|
-
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
|
|
7215
|
-
if (elapsedSeconds >= 1) {
|
|
7216
|
-
wrappedStreamCallback(` (${elapsedSeconds}s)`);
|
|
7217
|
-
}
|
|
7218
|
-
}, intervalMs);
|
|
7219
|
-
try {
|
|
7220
|
-
const result2 = await operation();
|
|
7221
|
-
return result2;
|
|
7222
|
-
} finally {
|
|
7223
|
-
clearInterval(heartbeatInterval);
|
|
7224
|
-
wrappedStreamCallback("\n\n");
|
|
7225
|
-
}
|
|
7226
|
-
};
|
|
7227
|
-
const toolHandler = async (toolName, toolInput) => {
|
|
7228
|
-
if (toolName === "execute_query") {
|
|
7229
|
-
let sql = toolInput.sql;
|
|
7230
|
-
const params = toolInput.params || {};
|
|
7231
|
-
const reasoning = toolInput.reasoning;
|
|
7232
|
-
sql = ensureQueryLimit(sql, 10, 10);
|
|
7233
|
-
const queryKey = sql.toLowerCase().replace(/\s+/g, " ").trim();
|
|
7234
|
-
const attempts = (queryAttempts.get(queryKey) || 0) + 1;
|
|
7235
|
-
queryAttempts.set(queryKey, attempts);
|
|
7236
|
-
logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${sql.substring(0, 100)}...`);
|
|
7237
|
-
if (Object.keys(params).length > 0) {
|
|
7238
|
-
logger.info(`[${this.getProviderName()}] Query params: ${JSON.stringify(params)}`);
|
|
7239
|
-
}
|
|
7240
|
-
if (reasoning) {
|
|
7241
|
-
logCollector?.info(`Query reasoning: ${reasoning}`);
|
|
7242
|
-
}
|
|
7243
|
-
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
7244
|
-
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
7245
|
-
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
7246
|
-
logCollector?.error(errorMsg);
|
|
7247
|
-
maxAttemptsReached = true;
|
|
7248
|
-
if (wrappedStreamCallback) {
|
|
7249
|
-
wrappedStreamCallback(`
|
|
7250
|
-
|
|
7251
|
-
\u274C ${errorMsg}
|
|
7252
|
-
|
|
7253
|
-
Please try rephrasing your question or simplifying your request.
|
|
7254
|
-
|
|
7255
|
-
`);
|
|
7256
|
-
}
|
|
7257
|
-
throw new Error(errorMsg);
|
|
7258
|
-
}
|
|
7259
|
-
try {
|
|
7260
|
-
flushStream();
|
|
7261
|
-
if (wrappedStreamCallback) {
|
|
7262
|
-
const paramsDisplay = Object.keys(params).length > 0 ? `
|
|
7263
|
-
**Parameters:** ${JSON.stringify(params)}` : "";
|
|
7264
|
-
if (attempts === 1) {
|
|
7265
|
-
wrappedStreamCallback(`
|
|
7266
|
-
|
|
7267
|
-
\u{1F50D} **Analyzing your question...**
|
|
7268
|
-
|
|
7269
|
-
`);
|
|
7270
|
-
await streamDelay(50);
|
|
7271
|
-
if (reasoning) {
|
|
7272
|
-
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
7273
|
-
|
|
7274
|
-
`);
|
|
7275
|
-
await streamDelay(50);
|
|
7276
|
-
}
|
|
7277
|
-
wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
|
|
7278
|
-
\`\`\`sql
|
|
7279
|
-
${sql}
|
|
7280
|
-
\`\`\`${paramsDisplay}
|
|
7281
|
-
|
|
7282
|
-
`);
|
|
7283
|
-
await streamDelay(50);
|
|
7284
|
-
} else {
|
|
7285
|
-
wrappedStreamCallback(`
|
|
7286
|
-
|
|
7287
|
-
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
7288
|
-
|
|
7289
|
-
`);
|
|
7290
|
-
await streamDelay(50);
|
|
7291
|
-
if (reasoning) {
|
|
7292
|
-
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
7293
|
-
|
|
7294
|
-
`);
|
|
7295
|
-
await streamDelay(50);
|
|
7296
|
-
}
|
|
7297
|
-
wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
|
|
7298
|
-
\`\`\`sql
|
|
7299
|
-
${sql}
|
|
7300
|
-
\`\`\`${paramsDisplay}
|
|
7301
|
-
|
|
7302
|
-
`);
|
|
7303
|
-
await streamDelay(50);
|
|
7304
|
-
}
|
|
7305
|
-
}
|
|
7306
|
-
logCollector?.logQuery(
|
|
7307
|
-
`Executing SQL query (attempt ${attempts})`,
|
|
7308
|
-
{ sql, params },
|
|
7309
|
-
{ reasoning, attempt: attempts }
|
|
7310
|
-
);
|
|
7311
|
-
if (!collections || !collections["database"] || !collections["database"]["execute"]) {
|
|
7312
|
-
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
7313
|
-
}
|
|
7314
|
-
const queryPayload = Object.keys(params).length > 0 ? { sql: JSON.stringify({ sql, values: params }) } : { sql };
|
|
7315
|
-
const result2 = await withProgressHeartbeat(
|
|
7316
|
-
() => collections["database"]["execute"](queryPayload),
|
|
7317
|
-
"Executing database query",
|
|
7318
|
-
800
|
|
7319
|
-
// Send heartbeat every 800ms for responsive feedback
|
|
7320
|
-
);
|
|
7321
|
-
const data = result2?.data || result2;
|
|
7322
|
-
const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
7323
|
-
logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
|
|
7324
|
-
logCollector?.info(`Query successful, returned ${rowCount} rows`);
|
|
7325
|
-
if (wrappedStreamCallback) {
|
|
7326
|
-
wrappedStreamCallback(`
|
|
7327
|
-
\u2705 **Query executed successfully!**
|
|
7328
|
-
|
|
7329
|
-
`);
|
|
7330
|
-
await streamDelay(50);
|
|
7331
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
7332
|
-
const firstRow = data[0];
|
|
7333
|
-
const columns = Object.keys(firstRow);
|
|
7334
|
-
if (data.length === 1 && columns.length === 1) {
|
|
7335
|
-
const value = firstRow[columns[0]];
|
|
7336
|
-
wrappedStreamCallback(`**Result:** ${value}
|
|
7337
|
-
|
|
7338
|
-
`);
|
|
7339
|
-
await streamDelay(50);
|
|
7340
|
-
} else if (data.length > 0) {
|
|
7341
|
-
wrappedStreamCallback(`**Retrieved ${rowCount} rows**
|
|
7342
|
-
|
|
7343
|
-
`);
|
|
7344
|
-
await streamDelay(50);
|
|
7345
|
-
wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
|
|
7346
|
-
|
|
7347
|
-
`);
|
|
7348
|
-
await streamDelay(50);
|
|
7349
|
-
}
|
|
7350
|
-
} else if (Array.isArray(data) && data.length === 0) {
|
|
7351
|
-
wrappedStreamCallback(`**No rows returned.**
|
|
7352
|
-
|
|
7353
|
-
`);
|
|
7354
|
-
await streamDelay(50);
|
|
7355
|
-
}
|
|
7356
|
-
wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
|
|
7357
|
-
|
|
7358
|
-
`);
|
|
7359
|
-
}
|
|
7360
|
-
return JSON.stringify(data, null, 2);
|
|
7361
|
-
} catch (error) {
|
|
7362
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7363
|
-
logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7364
|
-
logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
7365
|
-
userPromptErrorLogger.logSqlError(sql, error instanceof Error ? error : new Error(errorMsg), Object.keys(params).length > 0 ? Object.values(params) : void 0);
|
|
7366
|
-
if (wrappedStreamCallback) {
|
|
7367
|
-
wrappedStreamCallback(`\u274C **Query execution failed:**
|
|
7368
|
-
\`\`\`
|
|
7369
|
-
${errorMsg}
|
|
7370
|
-
\`\`\`
|
|
7371
|
-
|
|
7372
|
-
`);
|
|
7373
|
-
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
7374
|
-
wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
|
|
7375
|
-
|
|
7376
|
-
`);
|
|
7377
|
-
}
|
|
7378
|
-
}
|
|
7379
|
-
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
7380
|
-
}
|
|
7381
|
-
} else {
|
|
7382
|
-
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
7383
|
-
if (externalTool) {
|
|
7384
|
-
const attempts = (toolAttempts.get(toolName) || 0) + 1;
|
|
7385
|
-
toolAttempts.set(toolName, attempts);
|
|
7386
|
-
logger.info(`[${this.getProviderName()}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
|
|
7387
|
-
logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
|
|
7388
|
-
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
7389
|
-
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
7390
|
-
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
7391
|
-
logCollector?.error(errorMsg);
|
|
7392
|
-
if (wrappedStreamCallback) {
|
|
7393
|
-
wrappedStreamCallback(`
|
|
7394
|
-
|
|
7395
|
-
\u274C ${errorMsg}
|
|
7396
|
-
|
|
7397
|
-
Please try rephrasing your request or contact support.
|
|
7398
|
-
|
|
7399
|
-
`);
|
|
7400
|
-
}
|
|
7401
|
-
throw new Error(errorMsg);
|
|
7402
|
-
}
|
|
7403
|
-
try {
|
|
7404
|
-
flushStream();
|
|
7405
|
-
if (wrappedStreamCallback) {
|
|
7406
|
-
if (attempts === 1) {
|
|
7407
|
-
wrappedStreamCallback(`
|
|
7408
|
-
|
|
7409
|
-
\u{1F517} **Executing ${externalTool.name}...**
|
|
7410
|
-
|
|
7411
|
-
`);
|
|
7412
|
-
} else {
|
|
7413
|
-
wrappedStreamCallback(`
|
|
7414
|
-
|
|
7415
|
-
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
7416
|
-
|
|
7417
|
-
`);
|
|
7418
|
-
}
|
|
7419
|
-
await streamDelay(50);
|
|
7420
|
-
}
|
|
7421
|
-
const result2 = await withProgressHeartbeat(
|
|
7422
|
-
() => externalTool.fn(toolInput),
|
|
7423
|
-
`Running ${externalTool.name}`,
|
|
7424
|
-
800
|
|
7425
|
-
// Send heartbeat every 800ms
|
|
7426
|
-
);
|
|
7427
|
-
logger.info(`[${this.getProviderName()}] External tool ${externalTool.name} executed successfully`);
|
|
7428
|
-
logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
|
|
7429
|
-
if (!executedToolsList.find((t) => t.id === externalTool.id)) {
|
|
7430
|
-
let resultSummary = null;
|
|
7431
|
-
if (result2) {
|
|
7432
|
-
const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2);
|
|
7433
|
-
if (resultStr.length > 1e3) {
|
|
7434
|
-
resultSummary = {
|
|
7435
|
-
_preview: resultStr.substring(0, 1e3) + "... (truncated)",
|
|
7436
|
-
_totalLength: resultStr.length,
|
|
7437
|
-
_recordCount: Array.isArray(result2) ? result2.length : result2?.data?.length || result2?.contacts?.length || result2?.salesorders?.length || "unknown"
|
|
7438
|
-
};
|
|
7439
|
-
} else {
|
|
7440
|
-
resultSummary = result2;
|
|
7441
|
-
}
|
|
7442
|
-
}
|
|
7443
|
-
executedToolsList.push({
|
|
7444
|
-
id: externalTool.id,
|
|
7445
|
-
name: externalTool.name,
|
|
7446
|
-
params: toolInput,
|
|
7447
|
-
// The actual parameters used in this execution
|
|
7448
|
-
result: resultSummary,
|
|
7449
|
-
// Store summary instead of full result to save memory
|
|
7450
|
-
outputSchema: externalTool.outputSchema
|
|
7451
|
-
// Include output schema for component config generation
|
|
7452
|
-
});
|
|
7453
|
-
logger.info(`[${this.getProviderName()}] Tracked executed tool: ${externalTool.name} with params: ${JSON.stringify(toolInput)}`);
|
|
7454
|
-
}
|
|
7455
|
-
if (wrappedStreamCallback) {
|
|
7456
|
-
wrappedStreamCallback(`\u2705 **${externalTool.name} completed successfully**
|
|
7457
|
-
|
|
7458
|
-
`);
|
|
7459
|
-
await streamDelay(50);
|
|
7460
|
-
}
|
|
7461
|
-
return JSON.stringify(result2, null, 2);
|
|
7462
|
-
} catch (error) {
|
|
7463
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7464
|
-
logger.error(`[${this.getProviderName()}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
7465
|
-
logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
|
|
7466
|
-
userPromptErrorLogger.logToolError(externalTool.name, toolInput, error instanceof Error ? error : new Error(errorMsg));
|
|
7467
|
-
if (wrappedStreamCallback) {
|
|
7468
|
-
wrappedStreamCallback(`\u274C **${externalTool.name} failed:**
|
|
7469
|
-
\`\`\`
|
|
7470
|
-
${errorMsg}
|
|
7471
|
-
\`\`\`
|
|
7472
|
-
|
|
7473
|
-
`);
|
|
7474
|
-
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
7475
|
-
wrappedStreamCallback(`\u{1F527} **Retrying with adjusted parameters...**
|
|
7476
|
-
|
|
7477
|
-
`);
|
|
7478
|
-
}
|
|
7479
|
-
}
|
|
7480
|
-
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
7481
|
-
}
|
|
7482
|
-
}
|
|
7483
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
7484
|
-
}
|
|
7485
|
-
};
|
|
7911
|
+
logger.info(`[${this.getProviderName()}] Added ${addedToolIds.size} unique tool definitions from ${executableTools.length} tool calls (${externalTools.length - executableTools.length} deferred tools await form input)`);
|
|
7912
|
+
logger.debug(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
|
|
7913
|
+
}
|
|
7914
|
+
const streamBuffer = new StreamBuffer(streamCallback);
|
|
7915
|
+
const toolExecutor = new ToolExecutorService({
|
|
7916
|
+
providerName: this.getProviderName(),
|
|
7917
|
+
collections,
|
|
7918
|
+
streamBuffer
|
|
7919
|
+
});
|
|
7920
|
+
const executableExternalTools = externalTools?.map((t) => ({
|
|
7921
|
+
id: t.id,
|
|
7922
|
+
name: t.name,
|
|
7923
|
+
description: t.description,
|
|
7924
|
+
fn: t.fn,
|
|
7925
|
+
limit: t.limit,
|
|
7926
|
+
outputSchema: t.outputSchema,
|
|
7927
|
+
executionType: t.executionType,
|
|
7928
|
+
userProvidedData: t.userProvidedData
|
|
7929
|
+
})) || [];
|
|
7930
|
+
const toolHandler = toolExecutor.createToolHandler(executableExternalTools);
|
|
7486
7931
|
const result = await LLM.streamWithTools(
|
|
7487
7932
|
{
|
|
7488
7933
|
sys: prompts.system,
|
|
@@ -7492,21 +7937,17 @@ ${errorMsg}
|
|
|
7492
7937
|
toolHandler,
|
|
7493
7938
|
{
|
|
7494
7939
|
model: this.getModelForTask("complex"),
|
|
7495
|
-
maxTokens:
|
|
7940
|
+
maxTokens: MAX_TOKENS_TEXT_RESPONSE,
|
|
7496
7941
|
temperature: 0,
|
|
7497
7942
|
apiKey: this.getApiKey(apiKey),
|
|
7498
|
-
partial:
|
|
7499
|
-
// Pass the wrapped streaming callback to LLM
|
|
7943
|
+
partial: streamBuffer.hasCallback() ? (chunk) => streamBuffer.write(chunk) : void 0
|
|
7500
7944
|
},
|
|
7501
|
-
|
|
7502
|
-
// max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
|
|
7945
|
+
MAX_TOOL_CALLING_ITERATIONS
|
|
7503
7946
|
);
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
if (maxAttemptsReached) {
|
|
7947
|
+
const textResponse = streamBuffer.getFullText() || result || "I apologize, but I was unable to generate a response.";
|
|
7948
|
+
if (toolExecutor.isMaxAttemptsReached()) {
|
|
7507
7949
|
const methodDuration2 = Date.now() - methodStartTime;
|
|
7508
7950
|
logger.warn(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration2}ms | result: max attempts reached`);
|
|
7509
|
-
logCollector?.error("Failed to generate valid query after maximum attempts");
|
|
7510
7951
|
return {
|
|
7511
7952
|
success: false,
|
|
7512
7953
|
errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
|
|
@@ -7518,18 +7959,10 @@ ${errorMsg}
|
|
|
7518
7959
|
}
|
|
7519
7960
|
};
|
|
7520
7961
|
}
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
"
|
|
7524
|
-
"
|
|
7525
|
-
{
|
|
7526
|
-
textLength: textResponse.length
|
|
7527
|
-
}
|
|
7528
|
-
);
|
|
7529
|
-
flushStream();
|
|
7530
|
-
if (wrappedStreamCallback && components && components.length > 0 && category !== "general") {
|
|
7531
|
-
wrappedStreamCallback("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
|
|
7532
|
-
wrappedStreamCallback("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
|
|
7962
|
+
streamBuffer.flush();
|
|
7963
|
+
if (streamBuffer.hasCallback() && components && components.length > 0 && category !== "general") {
|
|
7964
|
+
streamBuffer.write("\n\n\u{1F4CA} **Generating visualization components...**\n\n");
|
|
7965
|
+
streamBuffer.write("__TEXT_COMPLETE__COMPONENT_GENERATION_START__");
|
|
7533
7966
|
}
|
|
7534
7967
|
let matchedComponents = [];
|
|
7535
7968
|
let layoutTitle = "Dashboard";
|
|
@@ -7537,8 +7970,6 @@ ${errorMsg}
|
|
|
7537
7970
|
let actions = [];
|
|
7538
7971
|
if (category === "general") {
|
|
7539
7972
|
logger.info(`[${this.getProviderName()}] Skipping component generation for general/conversational question`);
|
|
7540
|
-
logCollector?.info("Skipping component generation for general question");
|
|
7541
|
-
logger.info(`[${this.getProviderName()}] Generating actions for general question...`);
|
|
7542
7973
|
const nextQuestions = await this.generateNextQuestions(
|
|
7543
7974
|
userPrompt,
|
|
7544
7975
|
null,
|
|
@@ -7546,23 +7977,16 @@ ${errorMsg}
|
|
|
7546
7977
|
void 0,
|
|
7547
7978
|
// no component data
|
|
7548
7979
|
apiKey,
|
|
7549
|
-
logCollector,
|
|
7550
7980
|
conversationHistory,
|
|
7551
7981
|
textResponse
|
|
7552
7982
|
// pass text response as context
|
|
7553
7983
|
);
|
|
7554
7984
|
actions = convertQuestionsToActions(nextQuestions);
|
|
7555
|
-
logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions for general question`);
|
|
7556
7985
|
} else if (components && components.length > 0) {
|
|
7557
|
-
|
|
7558
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback setup: wrappedStreamCallback=${!!wrappedStreamCallback}, category=${category}`);
|
|
7559
|
-
const componentStreamCallback = wrappedStreamCallback && category !== "data_modification" ? (component) => {
|
|
7560
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback INVOKED for: ${component.name} (${component.type})`);
|
|
7986
|
+
const componentStreamCallback = streamBuffer.hasCallback() && category === "data_analysis" ? (component) => {
|
|
7561
7987
|
const answerMarker = `__ANSWER_COMPONENT_START__${JSON.stringify(component)}__ANSWER_COMPONENT_END__`;
|
|
7562
|
-
|
|
7563
|
-
logger.info(`[${this.getProviderName()}] Streamed answer component to frontend: ${component.name} (${component.type})`);
|
|
7988
|
+
streamBuffer.write(answerMarker);
|
|
7564
7989
|
} : void 0;
|
|
7565
|
-
logger.info(`[${this.getProviderName()}] componentStreamCallback created: ${!!componentStreamCallback}`);
|
|
7566
7990
|
const deferredTools = externalTools?.filter((t) => {
|
|
7567
7991
|
if (t.executionType === "deferred" && !t.userProvidedData) return true;
|
|
7568
7992
|
if (category === "data_modification" && !t.userProvidedData) {
|
|
@@ -7583,10 +8007,9 @@ ${errorMsg}
|
|
|
7583
8007
|
components,
|
|
7584
8008
|
userPrompt,
|
|
7585
8009
|
apiKey,
|
|
7586
|
-
logCollector,
|
|
7587
8010
|
componentStreamCallback,
|
|
7588
8011
|
deferredTools,
|
|
7589
|
-
|
|
8012
|
+
toolExecutor.getExecutedTools(),
|
|
7590
8013
|
collections,
|
|
7591
8014
|
userId
|
|
7592
8015
|
);
|
|
@@ -7597,8 +8020,6 @@ ${errorMsg}
|
|
|
7597
8020
|
}
|
|
7598
8021
|
let container_componet = null;
|
|
7599
8022
|
if (matchedComponents.length > 0) {
|
|
7600
|
-
logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
7601
|
-
logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
7602
8023
|
container_componet = {
|
|
7603
8024
|
id: `container_${Date.now()}`,
|
|
7604
8025
|
name: "MultiComponentContainer",
|
|
@@ -7630,7 +8051,6 @@ ${errorMsg}
|
|
|
7630
8051
|
const methodDuration = Date.now() - methodStartTime;
|
|
7631
8052
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7632
8053
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
7633
|
-
logCollector?.error(`Error generating text response: ${errorMsg}`);
|
|
7634
8054
|
userPromptErrorLogger.logLlmError(
|
|
7635
8055
|
this.getProviderName(),
|
|
7636
8056
|
this.model,
|
|
@@ -7658,38 +8078,29 @@ ${errorMsg}
|
|
|
7658
8078
|
* 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
|
|
7659
8079
|
* 3. Route appropriately based on category and response mode
|
|
7660
8080
|
*/
|
|
7661
|
-
async handleUserRequest(userPrompt, components, apiKey,
|
|
8081
|
+
async handleUserRequest(userPrompt, components, apiKey, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
|
|
7662
8082
|
const startTime = Date.now();
|
|
7663
|
-
logger.info(`[${this.getProviderName()}] handleUserRequest called for user prompt: ${userPrompt}`);
|
|
7664
|
-
logCollector?.info(`Starting request processing with mode: ${responseMode}`);
|
|
7665
8083
|
logger.clearFile();
|
|
7666
8084
|
logger.logLLMPrompt("handleUserRequest", "user", `User Prompt: ${userPrompt}`);
|
|
7667
8085
|
try {
|
|
7668
|
-
logger.info(`[${this.getProviderName()}] Step 1: Searching previous conversations...`);
|
|
7669
8086
|
const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
|
|
7670
8087
|
userPrompt,
|
|
7671
8088
|
collections,
|
|
7672
8089
|
userId,
|
|
7673
|
-
similarityThreshold:
|
|
8090
|
+
similarityThreshold: EXACT_MATCH_SIMILARITY_THRESHOLD
|
|
7674
8091
|
});
|
|
7675
8092
|
if (conversationMatch) {
|
|
7676
8093
|
logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
|
|
7677
|
-
logCollector?.info(
|
|
7678
|
-
`\u2713 Found similar conversation (${(conversationMatch.similarity * 100).toFixed(2)}% match)`
|
|
7679
|
-
);
|
|
7680
8094
|
const rawComponent = conversationMatch.uiBlock?.component || conversationMatch.uiBlock?.generatedComponentMetadata;
|
|
7681
8095
|
const isValidComponent = rawComponent && typeof rawComponent === "object" && Object.keys(rawComponent).length > 0;
|
|
7682
8096
|
const component = isValidComponent ? rawComponent : null;
|
|
7683
8097
|
const cachedTextResponse = conversationMatch.uiBlock?.analysis || conversationMatch.uiBlock?.textResponse || conversationMatch.uiBlock?.text || "";
|
|
7684
8098
|
if (this.containsFormComponent(component)) {
|
|
7685
8099
|
logger.info(`[${this.getProviderName()}] Skipping cached result - Form components contain stale defaultValues, fetching fresh data`);
|
|
7686
|
-
logCollector?.info("Skipping cache for form - fetching current values from database...");
|
|
7687
8100
|
} else if (!component) {
|
|
7688
|
-
if (conversationMatch.similarity >=
|
|
8101
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7689
8102
|
const elapsedTime2 = Date.now() - startTime;
|
|
7690
|
-
logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response`);
|
|
7691
|
-
logCollector?.info(`\u2713 Exact match for general question - returning cached response`);
|
|
7692
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
8103
|
+
logger.info(`[${this.getProviderName()}] \u2713 Exact match for general question - returning cached text response (${elapsedTime2}ms)`);
|
|
7693
8104
|
return {
|
|
7694
8105
|
success: true,
|
|
7695
8106
|
data: {
|
|
@@ -7704,14 +8115,11 @@ ${errorMsg}
|
|
|
7704
8115
|
};
|
|
7705
8116
|
} else {
|
|
7706
8117
|
logger.info(`[${this.getProviderName()}] Similar match but no component (general question) - processing fresh`);
|
|
7707
|
-
logCollector?.info("Similar match found but was a general conversation - processing as new question");
|
|
7708
8118
|
}
|
|
7709
8119
|
} else {
|
|
7710
|
-
if (conversationMatch.similarity >=
|
|
8120
|
+
if (conversationMatch.similarity >= EXACT_MATCH_SIMILARITY_THRESHOLD) {
|
|
7711
8121
|
const elapsedTime2 = Date.now() - startTime;
|
|
7712
|
-
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
|
|
7713
|
-
logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
|
|
7714
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
8122
|
+
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation (${elapsedTime2}ms)`);
|
|
7715
8123
|
if (streamCallback && cachedTextResponse) {
|
|
7716
8124
|
logger.info(`[${this.getProviderName()}] Streaming cached text response to frontend`);
|
|
7717
8125
|
streamCallback(cachedTextResponse);
|
|
@@ -7730,22 +8138,18 @@ ${errorMsg}
|
|
|
7730
8138
|
errors: []
|
|
7731
8139
|
};
|
|
7732
8140
|
}
|
|
7733
|
-
|
|
8141
|
+
logger.info(`[${this.getProviderName()}] Adapting parameters for similar question...`);
|
|
7734
8142
|
const originalPrompt = conversationMatch.metadata?.userPrompt || "Previous question";
|
|
7735
8143
|
const adaptResult = await this.adaptUIBlockParameters(
|
|
7736
8144
|
userPrompt,
|
|
7737
8145
|
originalPrompt,
|
|
7738
8146
|
conversationMatch.uiBlock,
|
|
7739
8147
|
apiKey,
|
|
7740
|
-
logCollector,
|
|
7741
8148
|
cachedTextResponse
|
|
7742
8149
|
);
|
|
7743
8150
|
if (adaptResult.success && adaptResult.adaptedComponent) {
|
|
7744
8151
|
const elapsedTime2 = Date.now() - startTime;
|
|
7745
|
-
logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters`);
|
|
7746
|
-
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
7747
|
-
logCollector?.info(`\u2713 UI block adapted successfully`);
|
|
7748
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
8152
|
+
logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters (${elapsedTime2}ms)`);
|
|
7749
8153
|
const textResponseToUse = adaptResult.adaptedTextResponse || cachedTextResponse;
|
|
7750
8154
|
if (streamCallback && textResponseToUse) {
|
|
7751
8155
|
logger.info(`[${this.getProviderName()}] Streaming ${adaptResult.adaptedTextResponse ? "adapted" : "cached"} text response to frontend`);
|
|
@@ -7766,65 +8170,57 @@ ${errorMsg}
|
|
|
7766
8170
|
errors: []
|
|
7767
8171
|
};
|
|
7768
8172
|
} else {
|
|
7769
|
-
logger.info(`[${this.getProviderName()}] Could not adapt matched conversation, continuing to category classification`);
|
|
7770
|
-
logCollector?.warn(`Could not adapt matched conversation: ${adaptResult.explanation}`);
|
|
8173
|
+
logger.info(`[${this.getProviderName()}] Could not adapt matched conversation: ${adaptResult.explanation}, continuing to category classification`);
|
|
7771
8174
|
}
|
|
7772
8175
|
}
|
|
7773
8176
|
} else {
|
|
7774
8177
|
logger.info(`[${this.getProviderName()}] No matching previous conversations found, proceeding to category classification`);
|
|
7775
|
-
logCollector?.info("No similar previous conversations found. Proceeding to category classification...");
|
|
7776
8178
|
}
|
|
7777
8179
|
logger.info(`[${this.getProviderName()}] Step 2: Classifying question category...`);
|
|
7778
|
-
logCollector?.info("Step 2: Classifying question category...");
|
|
7779
8180
|
const categoryClassification = await this.classifyQuestionCategory(
|
|
7780
8181
|
userPrompt,
|
|
7781
8182
|
apiKey,
|
|
7782
|
-
logCollector,
|
|
7783
8183
|
conversationHistory,
|
|
7784
8184
|
externalTools
|
|
7785
8185
|
);
|
|
7786
8186
|
logger.info(
|
|
7787
8187
|
`[${this.getProviderName()}] Question classified as: ${categoryClassification.category} (confidence: ${categoryClassification.confidence}%)`
|
|
7788
8188
|
);
|
|
7789
|
-
logCollector?.info(
|
|
7790
|
-
`Category: ${categoryClassification.category} | Confidence: ${categoryClassification.confidence}%`
|
|
7791
|
-
);
|
|
7792
8189
|
let toolsToUse = [];
|
|
7793
8190
|
if (categoryClassification.externalTools && categoryClassification.externalTools.length > 0) {
|
|
7794
|
-
logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed`);
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
toolsToUse = categoryClassification.externalTools?.map((t) => {
|
|
8191
|
+
logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
|
|
8192
|
+
logger.debug(`[${this.getProviderName()}] Raw external tools from classification: ${JSON.stringify(categoryClassification.externalTools, null, 2)}`);
|
|
8193
|
+
toolsToUse = categoryClassification.externalTools.reduce((acc, t) => {
|
|
7798
8194
|
const realTool = externalTools?.find((tool) => tool.id === t.type);
|
|
7799
|
-
|
|
7800
|
-
|
|
8195
|
+
if (!realTool) {
|
|
8196
|
+
logger.warn(`[${this.getProviderName()}] Tool ${t.type} (${t.name}) not found in registered tools - skipping (likely hallucinated)`);
|
|
8197
|
+
return acc;
|
|
8198
|
+
}
|
|
8199
|
+
acc.push({
|
|
7801
8200
|
id: t.type,
|
|
7802
8201
|
name: t.name,
|
|
7803
8202
|
description: t.description,
|
|
7804
8203
|
params: t.parameters || {},
|
|
7805
|
-
//
|
|
8204
|
+
// Include execution type info from category classification
|
|
7806
8205
|
executionType: t.executionType || "immediate",
|
|
7807
8206
|
executionReason: t.executionReason || "",
|
|
7808
8207
|
requiredFields: t.requiredFields || [],
|
|
7809
8208
|
userProvidedData: t.userProvidedData || null,
|
|
7810
|
-
//
|
|
7811
|
-
outputSchema: realTool
|
|
7812
|
-
fn:
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
};
|
|
7822
|
-
}) || [];
|
|
8209
|
+
// Include outputSchema from real tool for component config generation
|
|
8210
|
+
outputSchema: realTool.outputSchema,
|
|
8211
|
+
fn: realTool.fn
|
|
8212
|
+
});
|
|
8213
|
+
return acc;
|
|
8214
|
+
}, []);
|
|
8215
|
+
const validCount = toolsToUse.length;
|
|
8216
|
+
const hallucinatedCount = categoryClassification.externalTools.length - validCount;
|
|
8217
|
+
if (hallucinatedCount > 0) {
|
|
8218
|
+
logger.warn(`[${this.getProviderName()}] Filtered out ${hallucinatedCount} hallucinated/non-existent tools, ${validCount} valid tools remaining`);
|
|
8219
|
+
}
|
|
7823
8220
|
}
|
|
7824
8221
|
const textResponse = await this.generateTextResponse(
|
|
7825
8222
|
userPrompt,
|
|
7826
8223
|
apiKey,
|
|
7827
|
-
logCollector,
|
|
7828
8224
|
conversationHistory,
|
|
7829
8225
|
streamCallback,
|
|
7830
8226
|
collections,
|
|
@@ -7835,13 +8231,10 @@ ${errorMsg}
|
|
|
7835
8231
|
);
|
|
7836
8232
|
const elapsedTime = Date.now() - startTime;
|
|
7837
8233
|
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7838
|
-
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7839
8234
|
return textResponse;
|
|
7840
8235
|
} catch (error) {
|
|
7841
8236
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7842
8237
|
logger.error(`[${this.getProviderName()}] Error in handleUserRequest: ${errorMsg}`);
|
|
7843
|
-
logger.debug(`[${this.getProviderName()}] Error details:`, error);
|
|
7844
|
-
logCollector?.error(`Error processing request: ${errorMsg}`);
|
|
7845
8238
|
userPromptErrorLogger.logError(
|
|
7846
8239
|
"handleUserRequest",
|
|
7847
8240
|
error instanceof Error ? error : new Error(errorMsg),
|
|
@@ -7849,7 +8242,6 @@ ${errorMsg}
|
|
|
7849
8242
|
);
|
|
7850
8243
|
const elapsedTime = Date.now() - startTime;
|
|
7851
8244
|
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7852
|
-
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
7853
8245
|
return {
|
|
7854
8246
|
success: false,
|
|
7855
8247
|
errors: [errorMsg],
|
|
@@ -7865,7 +8257,7 @@ ${errorMsg}
|
|
|
7865
8257
|
* This helps provide intelligent suggestions for follow-up queries
|
|
7866
8258
|
* For general/conversational questions without components, pass textResponse instead
|
|
7867
8259
|
*/
|
|
7868
|
-
async generateNextQuestions(originalUserPrompt, component, componentData, apiKey,
|
|
8260
|
+
async generateNextQuestions(originalUserPrompt, component, componentData, apiKey, conversationHistory, textResponse) {
|
|
7869
8261
|
const methodStartTime = Date.now();
|
|
7870
8262
|
const methodName = "generateNextQuestions";
|
|
7871
8263
|
const promptPreview = originalUserPrompt.substring(0, 50) + (originalUserPrompt.length > 50 ? "..." : "");
|
|
@@ -7902,7 +8294,7 @@ ${errorMsg}
|
|
|
7902
8294
|
},
|
|
7903
8295
|
{
|
|
7904
8296
|
model: this.getModelForTask("simple"),
|
|
7905
|
-
maxTokens:
|
|
8297
|
+
maxTokens: MAX_TOKENS_NEXT_QUESTIONS,
|
|
7906
8298
|
temperature: 0,
|
|
7907
8299
|
apiKey: this.getApiKey(apiKey)
|
|
7908
8300
|
},
|
|
@@ -7910,14 +8302,6 @@ ${errorMsg}
|
|
|
7910
8302
|
// Parse as JSON
|
|
7911
8303
|
);
|
|
7912
8304
|
const nextQuestions = result.nextQuestions || [];
|
|
7913
|
-
logCollector?.logExplanation(
|
|
7914
|
-
"Next questions generated",
|
|
7915
|
-
"Generated intelligent follow-up questions based on component",
|
|
7916
|
-
{
|
|
7917
|
-
count: nextQuestions.length,
|
|
7918
|
-
questions: nextQuestions
|
|
7919
|
-
}
|
|
7920
|
-
);
|
|
7921
8305
|
const methodDuration = Date.now() - methodStartTime;
|
|
7922
8306
|
logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | questions: ${nextQuestions.length}`);
|
|
7923
8307
|
return nextQuestions;
|
|
@@ -7925,8 +8309,6 @@ ${errorMsg}
|
|
|
7925
8309
|
const methodDuration = Date.now() - methodStartTime;
|
|
7926
8310
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7927
8311
|
logger.error(`[${this.getProviderName()}] [TIMING] FAILED ${methodName} in ${methodDuration}ms | error: ${errorMsg}`);
|
|
7928
|
-
logger.debug(`[${this.getProviderName()}] Next questions generation error details:`, error);
|
|
7929
|
-
logCollector?.error(`Error generating next questions: ${errorMsg}`);
|
|
7930
8312
|
return [];
|
|
7931
8313
|
}
|
|
7932
8314
|
}
|
|
@@ -8040,330 +8422,94 @@ function getLLMProviders() {
|
|
|
8040
8422
|
return DEFAULT_PROVIDERS;
|
|
8041
8423
|
}
|
|
8042
8424
|
}
|
|
8043
|
-
var useAnthropicMethod = async (prompt, components, apiKey,
|
|
8044
|
-
logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
|
|
8045
|
-
logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
|
|
8046
|
-
const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8047
|
-
logCollector?.info(msg);
|
|
8425
|
+
var useAnthropicMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8048
8426
|
if (responseMode === "component" && components.length === 0) {
|
|
8049
8427
|
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8050
8428
|
logger.error("[useAnthropicMethod] No components available");
|
|
8051
|
-
logCollector?.error(emptyMsg);
|
|
8052
8429
|
return { success: false, errors: [emptyMsg] };
|
|
8053
8430
|
}
|
|
8054
|
-
|
|
8055
|
-
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8431
|
+
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8056
8432
|
logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
|
|
8057
8433
|
return matchResult;
|
|
8058
8434
|
};
|
|
8059
|
-
var useGroqMethod = async (prompt, components, apiKey,
|
|
8435
|
+
var useGroqMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8060
8436
|
logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
|
|
8061
8437
|
logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
|
|
8062
|
-
const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8063
|
-
logger.info(msg);
|
|
8064
|
-
logCollector?.info(msg);
|
|
8065
|
-
if (responseMode === "component" && components.length === 0) {
|
|
8066
|
-
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8067
|
-
logger.error("[useGroqMethod] No components available");
|
|
8068
|
-
logCollector?.error(emptyMsg);
|
|
8069
|
-
return { success: false, errors: [emptyMsg] };
|
|
8070
|
-
}
|
|
8071
|
-
logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
|
|
8072
|
-
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8073
|
-
logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
|
|
8074
|
-
return matchResult;
|
|
8075
|
-
};
|
|
8076
|
-
var useGeminiMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8077
|
-
logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
|
|
8078
|
-
logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
|
|
8079
|
-
const msg = `Using Gemini LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8080
|
-
logger.info(msg);
|
|
8081
|
-
logCollector?.info(msg);
|
|
8082
|
-
if (responseMode === "component" && components.length === 0) {
|
|
8083
|
-
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8084
|
-
logger.error("[useGeminiMethod] No components available");
|
|
8085
|
-
logCollector?.error(emptyMsg);
|
|
8086
|
-
return { success: false, errors: [emptyMsg] };
|
|
8087
|
-
}
|
|
8088
|
-
logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
|
|
8089
|
-
const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8090
|
-
logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
|
|
8091
|
-
return matchResult;
|
|
8092
|
-
};
|
|
8093
|
-
var useOpenAIMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8094
|
-
logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
|
|
8095
|
-
logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
|
|
8096
|
-
const msg = `Using OpenAI GPT ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
8097
|
-
logger.info(msg);
|
|
8098
|
-
logCollector?.info(msg);
|
|
8099
8438
|
if (responseMode === "component" && components.length === 0) {
|
|
8100
8439
|
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8101
|
-
logger.error("[
|
|
8102
|
-
|
|
8103
|
-
return { success: false, errors: [emptyMsg] };
|
|
8104
|
-
}
|
|
8105
|
-
logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
|
|
8106
|
-
const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8107
|
-
logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
|
|
8108
|
-
return matchResult;
|
|
8109
|
-
};
|
|
8110
|
-
var getUserResponseFromCache = async (prompt) => {
|
|
8111
|
-
return false;
|
|
8112
|
-
};
|
|
8113
|
-
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8114
|
-
logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
|
|
8115
|
-
logger.debug(`[get_user_response] Response mode: ${responseMode}`);
|
|
8116
|
-
logger.debug("[get_user_response] Checking cache for existing response");
|
|
8117
|
-
const userResponse = await getUserResponseFromCache(prompt);
|
|
8118
|
-
if (userResponse) {
|
|
8119
|
-
logger.info("[get_user_response] User response found in cache - returning cached result");
|
|
8120
|
-
logCollector?.info("User response found in cache");
|
|
8121
|
-
return {
|
|
8122
|
-
success: true,
|
|
8123
|
-
data: userResponse,
|
|
8124
|
-
errors: []
|
|
8125
|
-
};
|
|
8126
|
-
}
|
|
8127
|
-
logger.debug("[get_user_response] No cached response found, proceeding with LLM providers");
|
|
8128
|
-
const providers = llmProviders || getLLMProviders();
|
|
8129
|
-
const errors = [];
|
|
8130
|
-
const providerOrder = providers.join(", ");
|
|
8131
|
-
logCollector?.info(`LLM Provider order: [${providerOrder}]`);
|
|
8132
|
-
if (conversationHistory && conversationHistory.length > 0) {
|
|
8133
|
-
const exchangeCount = conversationHistory.split("\n").filter((l) => l.startsWith("Q")).length;
|
|
8134
|
-
logger.debug(`[get_user_response] Using conversation history with ${exchangeCount} previous exchanges`);
|
|
8135
|
-
logCollector?.info(`Using conversation history with ${exchangeCount} previous exchanges`);
|
|
8136
|
-
} else {
|
|
8137
|
-
logger.debug("[get_user_response] No conversation history available");
|
|
8138
|
-
}
|
|
8139
|
-
for (let i = 0; i < providers.length; i++) {
|
|
8140
|
-
const provider = providers[i];
|
|
8141
|
-
const isLastProvider = i === providers.length - 1;
|
|
8142
|
-
const attemptMsg = `Attempting provider: ${provider} (${i + 1}/${providers.length})`;
|
|
8143
|
-
logCollector?.info(attemptMsg);
|
|
8144
|
-
let result;
|
|
8145
|
-
if (provider === "anthropic") {
|
|
8146
|
-
result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8147
|
-
} else if (provider === "groq") {
|
|
8148
|
-
result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8149
|
-
} else if (provider === "gemini") {
|
|
8150
|
-
result = await useGeminiMethod(prompt, components, geminiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8151
|
-
} else if (provider === "openai") {
|
|
8152
|
-
result = await useOpenAIMethod(prompt, components, openaiApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8153
|
-
} else {
|
|
8154
|
-
logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
|
|
8155
|
-
errors.push(`Unknown provider: ${provider}`);
|
|
8156
|
-
continue;
|
|
8157
|
-
}
|
|
8158
|
-
if (result.success) {
|
|
8159
|
-
const successMsg = `Success with provider: ${provider}`;
|
|
8160
|
-
logger.info(`${successMsg}`);
|
|
8161
|
-
logCollector?.info(successMsg);
|
|
8162
|
-
return result;
|
|
8163
|
-
} else {
|
|
8164
|
-
const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
|
|
8165
|
-
errors.push(...providerErrors);
|
|
8166
|
-
const warnMsg = `Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`;
|
|
8167
|
-
logger.warn(`[get_user_response] ${warnMsg}`);
|
|
8168
|
-
logCollector?.warn(warnMsg);
|
|
8169
|
-
if (!isLastProvider) {
|
|
8170
|
-
const fallbackMsg = "Falling back to next provider...";
|
|
8171
|
-
logger.info(`[get_user_response] ${fallbackMsg}`);
|
|
8172
|
-
logCollector?.info(fallbackMsg);
|
|
8173
|
-
}
|
|
8174
|
-
}
|
|
8175
|
-
}
|
|
8176
|
-
const failureMsg = `All LLM providers failed`;
|
|
8177
|
-
logger.error(`[get_user_response] ${failureMsg}. Errors: ${errors.join("; ")}`);
|
|
8178
|
-
logCollector?.error(`${failureMsg}. Errors: ${errors.join("; ")}`);
|
|
8179
|
-
return {
|
|
8180
|
-
success: false,
|
|
8181
|
-
errors
|
|
8182
|
-
};
|
|
8183
|
-
};
|
|
8184
|
-
|
|
8185
|
-
// src/utils/log-collector.ts
|
|
8186
|
-
var LOG_LEVEL_PRIORITY2 = {
|
|
8187
|
-
errors: 0,
|
|
8188
|
-
warnings: 1,
|
|
8189
|
-
info: 2,
|
|
8190
|
-
verbose: 3
|
|
8191
|
-
};
|
|
8192
|
-
var MESSAGE_LEVEL_PRIORITY2 = {
|
|
8193
|
-
error: 0,
|
|
8194
|
-
warn: 1,
|
|
8195
|
-
info: 2,
|
|
8196
|
-
debug: 3
|
|
8197
|
-
};
|
|
8198
|
-
var UILogCollector = class {
|
|
8199
|
-
constructor(clientId, sendMessage, uiBlockId) {
|
|
8200
|
-
this.logs = [];
|
|
8201
|
-
this.uiBlockId = uiBlockId || null;
|
|
8202
|
-
this.clientId = clientId;
|
|
8203
|
-
this.sendMessage = sendMessage;
|
|
8204
|
-
this.currentLogLevel = logger.getLogLevel();
|
|
8205
|
-
}
|
|
8206
|
-
/**
|
|
8207
|
-
* Check if logging is enabled (uiBlockId is provided)
|
|
8208
|
-
*/
|
|
8209
|
-
isEnabled() {
|
|
8210
|
-
return this.uiBlockId !== null;
|
|
8211
|
-
}
|
|
8212
|
-
/**
|
|
8213
|
-
* Check if a message should be logged based on current log level
|
|
8214
|
-
*/
|
|
8215
|
-
shouldLog(messageLevel) {
|
|
8216
|
-
const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
|
|
8217
|
-
const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
|
|
8218
|
-
return messagePriority <= currentLevelPriority;
|
|
8219
|
-
}
|
|
8220
|
-
/**
|
|
8221
|
-
* Add a log entry with timestamp and immediately send to runtime
|
|
8222
|
-
* Only logs that pass the log level filter are captured and sent
|
|
8223
|
-
*/
|
|
8224
|
-
addLog(level, message, type, data) {
|
|
8225
|
-
if (!this.shouldLog(level)) {
|
|
8226
|
-
return;
|
|
8227
|
-
}
|
|
8228
|
-
const log = {
|
|
8229
|
-
timestamp: Date.now(),
|
|
8230
|
-
level,
|
|
8231
|
-
message,
|
|
8232
|
-
...type && { type },
|
|
8233
|
-
...data && { data }
|
|
8234
|
-
};
|
|
8235
|
-
this.logs.push(log);
|
|
8236
|
-
this.sendLogImmediately(log);
|
|
8237
|
-
switch (level) {
|
|
8238
|
-
case "error":
|
|
8239
|
-
logger.error("UILogCollector:", log);
|
|
8240
|
-
break;
|
|
8241
|
-
case "warn":
|
|
8242
|
-
logger.warn("UILogCollector:", log);
|
|
8243
|
-
break;
|
|
8244
|
-
case "info":
|
|
8245
|
-
logger.info("UILogCollector:", log);
|
|
8246
|
-
break;
|
|
8247
|
-
case "debug":
|
|
8248
|
-
logger.debug("UILogCollector:", log);
|
|
8249
|
-
break;
|
|
8250
|
-
}
|
|
8251
|
-
}
|
|
8252
|
-
/**
|
|
8253
|
-
* Send a single log to runtime immediately
|
|
8254
|
-
*/
|
|
8255
|
-
sendLogImmediately(log) {
|
|
8256
|
-
if (!this.isEnabled()) {
|
|
8257
|
-
return;
|
|
8258
|
-
}
|
|
8259
|
-
const response = {
|
|
8260
|
-
id: this.uiBlockId,
|
|
8261
|
-
type: "UI_LOGS",
|
|
8262
|
-
from: { type: "data-agent" },
|
|
8263
|
-
to: {
|
|
8264
|
-
type: "runtime",
|
|
8265
|
-
id: this.clientId
|
|
8266
|
-
},
|
|
8267
|
-
payload: {
|
|
8268
|
-
logs: [log]
|
|
8269
|
-
// Send single log in array
|
|
8270
|
-
}
|
|
8271
|
-
};
|
|
8272
|
-
this.sendMessage(response);
|
|
8273
|
-
}
|
|
8274
|
-
/**
|
|
8275
|
-
* Log info message
|
|
8276
|
-
*/
|
|
8277
|
-
info(message, type, data) {
|
|
8278
|
-
if (this.isEnabled()) {
|
|
8279
|
-
this.addLog("info", message, type, data);
|
|
8280
|
-
}
|
|
8281
|
-
}
|
|
8282
|
-
/**
|
|
8283
|
-
* Log error message
|
|
8284
|
-
*/
|
|
8285
|
-
error(message, type, data) {
|
|
8286
|
-
if (this.isEnabled()) {
|
|
8287
|
-
this.addLog("error", message, type, data);
|
|
8288
|
-
}
|
|
8289
|
-
}
|
|
8290
|
-
/**
|
|
8291
|
-
* Log warning message
|
|
8292
|
-
*/
|
|
8293
|
-
warn(message, type, data) {
|
|
8294
|
-
if (this.isEnabled()) {
|
|
8295
|
-
this.addLog("warn", message, type, data);
|
|
8296
|
-
}
|
|
8297
|
-
}
|
|
8298
|
-
/**
|
|
8299
|
-
* Log debug message
|
|
8300
|
-
*/
|
|
8301
|
-
debug(message, type, data) {
|
|
8302
|
-
if (this.isEnabled()) {
|
|
8303
|
-
this.addLog("debug", message, type, data);
|
|
8304
|
-
}
|
|
8305
|
-
}
|
|
8306
|
-
/**
|
|
8307
|
-
* Log LLM explanation with typed metadata
|
|
8308
|
-
*/
|
|
8309
|
-
logExplanation(message, explanation, data) {
|
|
8310
|
-
if (this.isEnabled()) {
|
|
8311
|
-
this.addLog("info", message, "explanation", {
|
|
8312
|
-
explanation,
|
|
8313
|
-
...data
|
|
8314
|
-
});
|
|
8315
|
-
}
|
|
8316
|
-
}
|
|
8317
|
-
/**
|
|
8318
|
-
* Log generated query with typed metadata
|
|
8319
|
-
*/
|
|
8320
|
-
logQuery(message, query, data) {
|
|
8321
|
-
if (this.isEnabled()) {
|
|
8322
|
-
this.addLog("info", message, "query", {
|
|
8323
|
-
query,
|
|
8324
|
-
...data
|
|
8325
|
-
});
|
|
8326
|
-
}
|
|
8327
|
-
}
|
|
8328
|
-
/**
|
|
8329
|
-
* Send all collected logs at once (optional, for final summary)
|
|
8330
|
-
*/
|
|
8331
|
-
sendAllLogs() {
|
|
8332
|
-
if (!this.isEnabled() || this.logs.length === 0) {
|
|
8333
|
-
return;
|
|
8334
|
-
}
|
|
8335
|
-
const response = {
|
|
8336
|
-
id: this.uiBlockId,
|
|
8337
|
-
type: "UI_LOGS",
|
|
8338
|
-
from: { type: "data-agent" },
|
|
8339
|
-
to: {
|
|
8340
|
-
type: "runtime",
|
|
8341
|
-
id: this.clientId
|
|
8342
|
-
},
|
|
8343
|
-
payload: {
|
|
8344
|
-
logs: this.logs
|
|
8345
|
-
}
|
|
8346
|
-
};
|
|
8347
|
-
this.sendMessage(response);
|
|
8348
|
-
}
|
|
8349
|
-
/**
|
|
8350
|
-
* Get all collected logs
|
|
8351
|
-
*/
|
|
8352
|
-
getLogs() {
|
|
8353
|
-
return [...this.logs];
|
|
8440
|
+
logger.error("[useGroqMethod] No components available");
|
|
8441
|
+
return { success: false, errors: [emptyMsg] };
|
|
8354
8442
|
}
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8443
|
+
logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
|
|
8444
|
+
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8445
|
+
logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
|
|
8446
|
+
return matchResult;
|
|
8447
|
+
};
|
|
8448
|
+
var useGeminiMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8449
|
+
logger.debug("[useGeminiMethod] Initializing Gemini LLM matching method");
|
|
8450
|
+
logger.debug(`[useGeminiMethod] Response mode: ${responseMode}`);
|
|
8451
|
+
if (responseMode === "component" && components.length === 0) {
|
|
8452
|
+
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8453
|
+
logger.error("[useGeminiMethod] No components available");
|
|
8454
|
+
return { success: false, errors: [emptyMsg] };
|
|
8360
8455
|
}
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8456
|
+
logger.debug(`[useGeminiMethod] Processing with ${components.length} components`);
|
|
8457
|
+
const matchResult = await geminiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8458
|
+
logger.info(`[useGeminiMethod] Successfully generated ${responseMode} using Gemini`);
|
|
8459
|
+
return matchResult;
|
|
8460
|
+
};
|
|
8461
|
+
var useOpenAIMethod = async (prompt, components, apiKey, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8462
|
+
logger.debug("[useOpenAIMethod] Initializing OpenAI GPT matching method");
|
|
8463
|
+
logger.debug(`[useOpenAIMethod] Response mode: ${responseMode}`);
|
|
8464
|
+
if (responseMode === "component" && components.length === 0) {
|
|
8465
|
+
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
8466
|
+
logger.error("[useOpenAIMethod] No components available");
|
|
8467
|
+
return { success: false, errors: [emptyMsg] };
|
|
8366
8468
|
}
|
|
8469
|
+
logger.debug(`[useOpenAIMethod] Processing with ${components.length} components`);
|
|
8470
|
+
const matchResult = await openaiLLM.handleUserRequest(prompt, components, apiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8471
|
+
logger.info(`[useOpenAIMethod] Successfully generated ${responseMode} using OpenAI`);
|
|
8472
|
+
return matchResult;
|
|
8473
|
+
};
|
|
8474
|
+
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
8475
|
+
const providers = llmProviders || getLLMProviders();
|
|
8476
|
+
const errors = [];
|
|
8477
|
+
logger.info(`[get_user_response] LLM Provider order: [${providers.join(", ")}]`);
|
|
8478
|
+
for (let i = 0; i < providers.length; i++) {
|
|
8479
|
+
const provider = providers[i];
|
|
8480
|
+
const isLastProvider = i === providers.length - 1;
|
|
8481
|
+
logger.info(`[get_user_response] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
|
|
8482
|
+
let result;
|
|
8483
|
+
if (provider === "anthropic") {
|
|
8484
|
+
result = await useAnthropicMethod(prompt, components, anthropicApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8485
|
+
} else if (provider === "groq") {
|
|
8486
|
+
result = await useGroqMethod(prompt, components, groqApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8487
|
+
} else if (provider === "gemini") {
|
|
8488
|
+
result = await useGeminiMethod(prompt, components, geminiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8489
|
+
} else if (provider === "openai") {
|
|
8490
|
+
result = await useOpenAIMethod(prompt, components, openaiApiKey, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
8491
|
+
} else {
|
|
8492
|
+
logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
|
|
8493
|
+
errors.push(`Unknown provider: ${provider}`);
|
|
8494
|
+
continue;
|
|
8495
|
+
}
|
|
8496
|
+
if (result.success) {
|
|
8497
|
+
logger.info(`[get_user_response] Success with provider: ${provider}`);
|
|
8498
|
+
return result;
|
|
8499
|
+
} else {
|
|
8500
|
+
const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
|
|
8501
|
+
errors.push(...providerErrors);
|
|
8502
|
+
logger.warn(`[get_user_response] Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`);
|
|
8503
|
+
if (!isLastProvider) {
|
|
8504
|
+
logger.info("[get_user_response] Falling back to next provider...");
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
}
|
|
8508
|
+
logger.error(`[get_user_response] All LLM providers failed. Errors: ${errors.join("; ")}`);
|
|
8509
|
+
return {
|
|
8510
|
+
success: false,
|
|
8511
|
+
errors
|
|
8512
|
+
};
|
|
8367
8513
|
};
|
|
8368
8514
|
|
|
8369
8515
|
// src/utils/conversation-saver.ts
|
|
@@ -8496,7 +8642,6 @@ var CONTEXT_CONFIG = {
|
|
|
8496
8642
|
// src/handlers/user-prompt-request.ts
|
|
8497
8643
|
var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools) => {
|
|
8498
8644
|
const errors = [];
|
|
8499
|
-
logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
|
|
8500
8645
|
const parseResult = UserPromptRequestMessageSchema.safeParse(data);
|
|
8501
8646
|
if (!parseResult.success) {
|
|
8502
8647
|
const zodError = parseResult.error;
|
|
@@ -8528,27 +8673,23 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8528
8673
|
if (!prompt) {
|
|
8529
8674
|
errors.push("Prompt not found");
|
|
8530
8675
|
}
|
|
8531
|
-
logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
|
|
8532
8676
|
if (errors.length > 0) {
|
|
8533
8677
|
return { success: false, errors, id, wsId };
|
|
8534
8678
|
}
|
|
8535
|
-
const logCollector = new UILogCollector(wsId, sendMessage, existingUiBlockId);
|
|
8536
8679
|
const threadManager = ThreadManager.getInstance();
|
|
8537
8680
|
let thread = threadManager.getThread(threadId);
|
|
8538
8681
|
if (!thread) {
|
|
8539
8682
|
thread = threadManager.createThread(threadId);
|
|
8540
8683
|
logger.info(`Created new thread: ${threadId}`);
|
|
8541
8684
|
}
|
|
8542
|
-
|
|
8685
|
+
logger.info(`Starting user prompt request with ${components.length} components`);
|
|
8543
8686
|
const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
|
|
8544
8687
|
const responseMode = payload.responseMode || "component";
|
|
8545
|
-
logger.info("responseMode", responseMode);
|
|
8546
8688
|
let streamCallback;
|
|
8547
8689
|
let accumulatedStreamResponse = "";
|
|
8548
8690
|
if (responseMode === "text") {
|
|
8549
8691
|
streamCallback = (chunk) => {
|
|
8550
8692
|
accumulatedStreamResponse += chunk;
|
|
8551
|
-
logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
|
|
8552
8693
|
const streamMessage = {
|
|
8553
8694
|
id: `stream_${existingUiBlockId}`,
|
|
8554
8695
|
// Different ID pattern for streaming
|
|
@@ -8564,7 +8705,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8564
8705
|
}
|
|
8565
8706
|
};
|
|
8566
8707
|
sendMessage(streamMessage);
|
|
8567
|
-
logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
|
|
8568
8708
|
};
|
|
8569
8709
|
}
|
|
8570
8710
|
const userResponse = await get_user_response(
|
|
@@ -8575,7 +8715,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8575
8715
|
geminiApiKey,
|
|
8576
8716
|
openaiApiKey,
|
|
8577
8717
|
llmProviders,
|
|
8578
|
-
logCollector,
|
|
8579
8718
|
conversationHistory,
|
|
8580
8719
|
responseMode,
|
|
8581
8720
|
streamCallback,
|
|
@@ -8583,7 +8722,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8583
8722
|
externalTools,
|
|
8584
8723
|
userId
|
|
8585
8724
|
);
|
|
8586
|
-
|
|
8725
|
+
logger.info("User prompt request completed");
|
|
8587
8726
|
const uiBlockId = existingUiBlockId;
|
|
8588
8727
|
if (!userResponse.success) {
|
|
8589
8728
|
logger.error(`User prompt request failed with errors: ${userResponse.errors.join(", ")}`);
|
|
@@ -8650,9 +8789,6 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
8650
8789
|
logger.info(
|
|
8651
8790
|
`Skipping conversation save - response from exact semantic match (${(semanticSimilarity * 100).toFixed(2)}% similarity)`
|
|
8652
8791
|
);
|
|
8653
|
-
logCollector.info(
|
|
8654
|
-
`Using exact cached result (${(semanticSimilarity * 100).toFixed(2)}% match) - not saving duplicate conversation`
|
|
8655
|
-
);
|
|
8656
8792
|
} else {
|
|
8657
8793
|
const uiBlockData = uiBlock.toJSON();
|
|
8658
8794
|
const saveResult = await saveConversation({
|
|
@@ -8860,7 +8996,7 @@ function sendResponse(id, res, sendMessage, clientId) {
|
|
|
8860
8996
|
}
|
|
8861
8997
|
|
|
8862
8998
|
// src/userResponse/next-questions.ts
|
|
8863
|
-
async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders,
|
|
8999
|
+
async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory) {
|
|
8864
9000
|
try {
|
|
8865
9001
|
logger.debug("[generateNextQuestions] Starting next questions generation");
|
|
8866
9002
|
logger.debug(`[generateNextQuestions] User prompt: "${originalUserPrompt?.substring(0, 50)}..."`);
|
|
@@ -8879,7 +9015,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8879
9015
|
const isLastProvider = i === providers.length - 1;
|
|
8880
9016
|
try {
|
|
8881
9017
|
logger.info(`[generateNextQuestions] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
|
|
8882
|
-
logCollector?.info(`Generating questions with ${provider}...`);
|
|
8883
9018
|
let result = [];
|
|
8884
9019
|
if (provider === "groq") {
|
|
8885
9020
|
logger.debug("[generateNextQuestions] Using Groq LLM for next questions");
|
|
@@ -8888,7 +9023,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8888
9023
|
component,
|
|
8889
9024
|
componentData,
|
|
8890
9025
|
groqApiKey,
|
|
8891
|
-
logCollector,
|
|
8892
9026
|
conversationHistory
|
|
8893
9027
|
);
|
|
8894
9028
|
} else if (provider === "gemini") {
|
|
@@ -8898,7 +9032,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8898
9032
|
component,
|
|
8899
9033
|
componentData,
|
|
8900
9034
|
geminiApiKey,
|
|
8901
|
-
logCollector,
|
|
8902
9035
|
conversationHistory
|
|
8903
9036
|
);
|
|
8904
9037
|
} else if (provider === "openai") {
|
|
@@ -8908,7 +9041,6 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8908
9041
|
component,
|
|
8909
9042
|
componentData,
|
|
8910
9043
|
openaiApiKey,
|
|
8911
|
-
logCollector,
|
|
8912
9044
|
conversationHistory
|
|
8913
9045
|
);
|
|
8914
9046
|
} else {
|
|
@@ -8918,44 +9050,32 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
8918
9050
|
component,
|
|
8919
9051
|
componentData,
|
|
8920
9052
|
anthropicApiKey,
|
|
8921
|
-
logCollector,
|
|
8922
9053
|
conversationHistory
|
|
8923
9054
|
);
|
|
8924
9055
|
}
|
|
8925
9056
|
if (result && result.length > 0) {
|
|
8926
9057
|
logger.info(`[generateNextQuestions] Successfully generated ${result.length} questions with ${provider}`);
|
|
8927
9058
|
logger.debug(`[generateNextQuestions] Questions: ${JSON.stringify(result)}`);
|
|
8928
|
-
logCollector?.info(`Generated ${result.length} follow-up questions`);
|
|
8929
9059
|
return result;
|
|
8930
9060
|
}
|
|
8931
|
-
|
|
8932
|
-
logger.warn(`[generateNextQuestions] ${warnMsg}`);
|
|
8933
|
-
if (!isLastProvider) {
|
|
8934
|
-
logCollector?.warn(warnMsg);
|
|
8935
|
-
}
|
|
9061
|
+
logger.warn(`[generateNextQuestions] No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`);
|
|
8936
9062
|
} catch (providerError) {
|
|
8937
9063
|
const errorMsg = providerError instanceof Error ? providerError.message : String(providerError);
|
|
8938
9064
|
logger.error(`[generateNextQuestions] Provider ${provider} failed: ${errorMsg}`);
|
|
8939
9065
|
logger.debug(`[generateNextQuestions] Provider error details:`, providerError);
|
|
8940
9066
|
if (!isLastProvider) {
|
|
8941
|
-
|
|
8942
|
-
logger.info(`[generateNextQuestions] ${fallbackMsg}`);
|
|
8943
|
-
logCollector?.warn(fallbackMsg);
|
|
8944
|
-
} else {
|
|
8945
|
-
logCollector?.error(`Failed to generate questions with ${provider}`);
|
|
9067
|
+
logger.info(`[generateNextQuestions] Provider ${provider} failed, trying next provider...`);
|
|
8946
9068
|
}
|
|
8947
9069
|
continue;
|
|
8948
9070
|
}
|
|
8949
9071
|
}
|
|
8950
9072
|
logger.warn("[generateNextQuestions] All providers failed or returned no questions");
|
|
8951
|
-
logCollector?.warn("Unable to generate follow-up questions");
|
|
8952
9073
|
return [];
|
|
8953
9074
|
} catch (error) {
|
|
8954
9075
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
8955
9076
|
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
8956
9077
|
logger.error(`[generateNextQuestions] Error generating next questions: ${errorMsg}`);
|
|
8957
9078
|
logger.debug("[generateNextQuestions] Error stack trace:", errorStack);
|
|
8958
|
-
logCollector?.error(`Error generating next questions: ${errorMsg}`);
|
|
8959
9079
|
return [];
|
|
8960
9080
|
}
|
|
8961
9081
|
}
|
|
@@ -9003,9 +9123,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
9003
9123
|
return;
|
|
9004
9124
|
}
|
|
9005
9125
|
logger.info(`[ACTIONS_REQ ${id}] UIBlock retrieved successfully`);
|
|
9006
|
-
logger.debug(`[ACTIONS_REQ ${id}] Creating UILogCollector for uiBlockId: ${uiBlockId}`);
|
|
9007
|
-
const logCollector = new UILogCollector(wsId, sendMessage, uiBlockId);
|
|
9008
|
-
logger.info(`[ACTIONS_REQ ${id}] UILogCollector initialized`);
|
|
9009
9126
|
logger.debug(`[ACTIONS_REQ ${id}] Extracting data from UIBlock`);
|
|
9010
9127
|
const userQuestion = uiBlock.getUserQuestion();
|
|
9011
9128
|
const component = uiBlock.getComponentMetadata();
|
|
@@ -9019,13 +9136,11 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
9019
9136
|
logger.info(`[ACTIONS_REQ ${id}] Conversation history extracted: ${historyLineCount} lines`);
|
|
9020
9137
|
logger.debug(`[ACTIONS_REQ ${id}] Conversation history preview:
|
|
9021
9138
|
${conversationHistory.substring(0, 200)}...`);
|
|
9022
|
-
|
|
9023
|
-
logger.info(`[ACTIONS_REQ ${id}] Generating actions for component: ${component?.name || "unknown"}`);
|
|
9139
|
+
logger.info(`[ACTIONS_REQ ${id}] Generating actions for UIBlock: ${uiBlockId}, component: ${component?.name || "unknown"}`);
|
|
9024
9140
|
logger.debug(`[ACTIONS_REQ ${id}] Checking if actions are already cached`);
|
|
9025
9141
|
const startTime = Date.now();
|
|
9026
9142
|
const actions = await uiBlock.getOrFetchActions(async () => {
|
|
9027
9143
|
logger.info(`[ACTIONS_REQ ${id}] Actions not cached, generating new actions...`);
|
|
9028
|
-
logCollector.info("Generating follow-up questions...");
|
|
9029
9144
|
logger.info(`[ACTIONS_REQ ${id}] Starting next questions generation with ${llmProviders?.join(", ") || "default"} providers`);
|
|
9030
9145
|
const nextQuestions = await generateNextQuestions(
|
|
9031
9146
|
userQuestion,
|
|
@@ -9036,7 +9151,6 @@ ${conversationHistory.substring(0, 200)}...`);
|
|
|
9036
9151
|
geminiApiKey,
|
|
9037
9152
|
openaiApiKey,
|
|
9038
9153
|
llmProviders,
|
|
9039
|
-
logCollector,
|
|
9040
9154
|
conversationHistory
|
|
9041
9155
|
);
|
|
9042
9156
|
logger.info(`[ACTIONS_REQ ${id}] Generated ${nextQuestions.length} questions`);
|
|
@@ -9054,11 +9168,10 @@ ${conversationHistory.substring(0, 200)}...`);
|
|
|
9054
9168
|
const processingTime = Date.now() - startTime;
|
|
9055
9169
|
logger.info(`[ACTIONS_REQ ${id}] Actions retrieved in ${processingTime}ms - ${actions.length} actions total`);
|
|
9056
9170
|
if (actions.length > 0) {
|
|
9057
|
-
|
|
9171
|
+
logger.info(`[ACTIONS_REQ ${id}] Generated ${actions.length} follow-up questions successfully`);
|
|
9058
9172
|
logger.debug(`[ACTIONS_REQ ${id}] Actions: ${actions.map((a) => a.name).join(", ")}`);
|
|
9059
9173
|
} else {
|
|
9060
9174
|
logger.warn(`[ACTIONS_REQ ${id}] No actions generated`);
|
|
9061
|
-
logCollector.warn("No follow-up questions could be generated");
|
|
9062
9175
|
}
|
|
9063
9176
|
logger.debug(`[ACTIONS_REQ ${id}] Sending successful response to client`);
|
|
9064
9177
|
sendResponse2(id, {
|
|
@@ -9077,15 +9190,6 @@ ${conversationHistory.substring(0, 200)}...`);
|
|
|
9077
9190
|
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
9078
9191
|
logger.error(`[ACTIONS_REQ] Failed to handle actions request: ${errorMessage}`);
|
|
9079
9192
|
logger.debug(`[ACTIONS_REQ] Error stack trace:`, errorStack);
|
|
9080
|
-
try {
|
|
9081
|
-
const parsedData = data;
|
|
9082
|
-
if (parsedData?.id && parsedData?.from?.id) {
|
|
9083
|
-
const logCollector = parsedData?.payload?.SA_RUNTIME?.uiBlockId ? new UILogCollector(parsedData.from.id, sendMessage, parsedData.payload.SA_RUNTIME.uiBlockId) : void 0;
|
|
9084
|
-
logCollector?.error(`Failed to generate actions: ${errorMessage}`);
|
|
9085
|
-
}
|
|
9086
|
-
} catch (logError) {
|
|
9087
|
-
logger.debug("[ACTIONS_REQ] Failed to send error logs to UI:", logError);
|
|
9088
|
-
}
|
|
9089
9193
|
sendResponse2(null, {
|
|
9090
9194
|
success: false,
|
|
9091
9195
|
error: errorMessage
|
|
@@ -9666,7 +9770,6 @@ function sendResponse3(id, res, sendMessage, clientId) {
|
|
|
9666
9770
|
var dashboardManager = null;
|
|
9667
9771
|
function setDashboardManager(manager) {
|
|
9668
9772
|
dashboardManager = manager;
|
|
9669
|
-
logger.info("DashboardManager instance set");
|
|
9670
9773
|
}
|
|
9671
9774
|
function getDashboardManager() {
|
|
9672
9775
|
if (!dashboardManager) {
|
|
@@ -12993,6 +13096,190 @@ var ReportManager = class {
|
|
|
12993
13096
|
}
|
|
12994
13097
|
};
|
|
12995
13098
|
|
|
13099
|
+
// src/utils/log-collector.ts
|
|
13100
|
+
var LOG_LEVEL_PRIORITY2 = {
|
|
13101
|
+
errors: 0,
|
|
13102
|
+
warnings: 1,
|
|
13103
|
+
info: 2,
|
|
13104
|
+
verbose: 3
|
|
13105
|
+
};
|
|
13106
|
+
var MESSAGE_LEVEL_PRIORITY2 = {
|
|
13107
|
+
error: 0,
|
|
13108
|
+
warn: 1,
|
|
13109
|
+
info: 2,
|
|
13110
|
+
debug: 3
|
|
13111
|
+
};
|
|
13112
|
+
var UILogCollector = class {
|
|
13113
|
+
constructor(clientId, sendMessage, uiBlockId) {
|
|
13114
|
+
this.logs = [];
|
|
13115
|
+
this.uiBlockId = uiBlockId || null;
|
|
13116
|
+
this.clientId = clientId;
|
|
13117
|
+
this.sendMessage = sendMessage;
|
|
13118
|
+
this.currentLogLevel = logger.getLogLevel();
|
|
13119
|
+
}
|
|
13120
|
+
/**
|
|
13121
|
+
* Check if logging is enabled (uiBlockId is provided)
|
|
13122
|
+
*/
|
|
13123
|
+
isEnabled() {
|
|
13124
|
+
return this.uiBlockId !== null;
|
|
13125
|
+
}
|
|
13126
|
+
/**
|
|
13127
|
+
* Check if a message should be logged based on current log level
|
|
13128
|
+
*/
|
|
13129
|
+
shouldLog(messageLevel) {
|
|
13130
|
+
const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
|
|
13131
|
+
const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
|
|
13132
|
+
return messagePriority <= currentLevelPriority;
|
|
13133
|
+
}
|
|
13134
|
+
/**
|
|
13135
|
+
* Add a log entry with timestamp and immediately send to runtime
|
|
13136
|
+
* Only logs that pass the log level filter are captured and sent
|
|
13137
|
+
*/
|
|
13138
|
+
addLog(level, message, type, data) {
|
|
13139
|
+
if (!this.shouldLog(level)) {
|
|
13140
|
+
return;
|
|
13141
|
+
}
|
|
13142
|
+
const log = {
|
|
13143
|
+
timestamp: Date.now(),
|
|
13144
|
+
level,
|
|
13145
|
+
message,
|
|
13146
|
+
...type && { type },
|
|
13147
|
+
...data && { data }
|
|
13148
|
+
};
|
|
13149
|
+
this.logs.push(log);
|
|
13150
|
+
this.sendLogImmediately(log);
|
|
13151
|
+
switch (level) {
|
|
13152
|
+
case "error":
|
|
13153
|
+
logger.error("UILogCollector:", log);
|
|
13154
|
+
break;
|
|
13155
|
+
case "warn":
|
|
13156
|
+
logger.warn("UILogCollector:", log);
|
|
13157
|
+
break;
|
|
13158
|
+
case "info":
|
|
13159
|
+
logger.info("UILogCollector:", log);
|
|
13160
|
+
break;
|
|
13161
|
+
case "debug":
|
|
13162
|
+
logger.debug("UILogCollector:", log);
|
|
13163
|
+
break;
|
|
13164
|
+
}
|
|
13165
|
+
}
|
|
13166
|
+
/**
|
|
13167
|
+
* Send a single log to runtime immediately
|
|
13168
|
+
*/
|
|
13169
|
+
sendLogImmediately(log) {
|
|
13170
|
+
if (!this.isEnabled()) {
|
|
13171
|
+
return;
|
|
13172
|
+
}
|
|
13173
|
+
const response = {
|
|
13174
|
+
id: this.uiBlockId,
|
|
13175
|
+
type: "UI_LOGS",
|
|
13176
|
+
from: { type: "data-agent" },
|
|
13177
|
+
to: {
|
|
13178
|
+
type: "runtime",
|
|
13179
|
+
id: this.clientId
|
|
13180
|
+
},
|
|
13181
|
+
payload: {
|
|
13182
|
+
logs: [log]
|
|
13183
|
+
// Send single log in array
|
|
13184
|
+
}
|
|
13185
|
+
};
|
|
13186
|
+
this.sendMessage(response);
|
|
13187
|
+
}
|
|
13188
|
+
/**
|
|
13189
|
+
* Log info message
|
|
13190
|
+
*/
|
|
13191
|
+
info(message, type, data) {
|
|
13192
|
+
if (this.isEnabled()) {
|
|
13193
|
+
this.addLog("info", message, type, data);
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
/**
|
|
13197
|
+
* Log error message
|
|
13198
|
+
*/
|
|
13199
|
+
error(message, type, data) {
|
|
13200
|
+
if (this.isEnabled()) {
|
|
13201
|
+
this.addLog("error", message, type, data);
|
|
13202
|
+
}
|
|
13203
|
+
}
|
|
13204
|
+
/**
|
|
13205
|
+
* Log warning message
|
|
13206
|
+
*/
|
|
13207
|
+
warn(message, type, data) {
|
|
13208
|
+
if (this.isEnabled()) {
|
|
13209
|
+
this.addLog("warn", message, type, data);
|
|
13210
|
+
}
|
|
13211
|
+
}
|
|
13212
|
+
/**
|
|
13213
|
+
* Log debug message
|
|
13214
|
+
*/
|
|
13215
|
+
debug(message, type, data) {
|
|
13216
|
+
if (this.isEnabled()) {
|
|
13217
|
+
this.addLog("debug", message, type, data);
|
|
13218
|
+
}
|
|
13219
|
+
}
|
|
13220
|
+
/**
|
|
13221
|
+
* Log LLM explanation with typed metadata
|
|
13222
|
+
*/
|
|
13223
|
+
logExplanation(message, explanation, data) {
|
|
13224
|
+
if (this.isEnabled()) {
|
|
13225
|
+
this.addLog("info", message, "explanation", {
|
|
13226
|
+
explanation,
|
|
13227
|
+
...data
|
|
13228
|
+
});
|
|
13229
|
+
}
|
|
13230
|
+
}
|
|
13231
|
+
/**
|
|
13232
|
+
* Log generated query with typed metadata
|
|
13233
|
+
*/
|
|
13234
|
+
logQuery(message, query, data) {
|
|
13235
|
+
if (this.isEnabled()) {
|
|
13236
|
+
this.addLog("info", message, "query", {
|
|
13237
|
+
query,
|
|
13238
|
+
...data
|
|
13239
|
+
});
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
/**
|
|
13243
|
+
* Send all collected logs at once (optional, for final summary)
|
|
13244
|
+
*/
|
|
13245
|
+
sendAllLogs() {
|
|
13246
|
+
if (!this.isEnabled() || this.logs.length === 0) {
|
|
13247
|
+
return;
|
|
13248
|
+
}
|
|
13249
|
+
const response = {
|
|
13250
|
+
id: this.uiBlockId,
|
|
13251
|
+
type: "UI_LOGS",
|
|
13252
|
+
from: { type: "data-agent" },
|
|
13253
|
+
to: {
|
|
13254
|
+
type: "runtime",
|
|
13255
|
+
id: this.clientId
|
|
13256
|
+
},
|
|
13257
|
+
payload: {
|
|
13258
|
+
logs: this.logs
|
|
13259
|
+
}
|
|
13260
|
+
};
|
|
13261
|
+
this.sendMessage(response);
|
|
13262
|
+
}
|
|
13263
|
+
/**
|
|
13264
|
+
* Get all collected logs
|
|
13265
|
+
*/
|
|
13266
|
+
getLogs() {
|
|
13267
|
+
return [...this.logs];
|
|
13268
|
+
}
|
|
13269
|
+
/**
|
|
13270
|
+
* Clear all logs
|
|
13271
|
+
*/
|
|
13272
|
+
clearLogs() {
|
|
13273
|
+
this.logs = [];
|
|
13274
|
+
}
|
|
13275
|
+
/**
|
|
13276
|
+
* Set uiBlockId (in case it's provided later)
|
|
13277
|
+
*/
|
|
13278
|
+
setUIBlockId(uiBlockId) {
|
|
13279
|
+
this.uiBlockId = uiBlockId;
|
|
13280
|
+
}
|
|
13281
|
+
};
|
|
13282
|
+
|
|
12996
13283
|
// src/services/cleanup-service.ts
|
|
12997
13284
|
var CleanupService = class _CleanupService {
|
|
12998
13285
|
constructor() {
|
|
@@ -13173,7 +13460,6 @@ var CleanupService = class _CleanupService {
|
|
|
13173
13460
|
};
|
|
13174
13461
|
|
|
13175
13462
|
// src/index.ts
|
|
13176
|
-
var SDK_VERSION = "0.0.8";
|
|
13177
13463
|
var DEFAULT_WS_URL = "wss://ws.superatom.ai/websocket";
|
|
13178
13464
|
var SuperatomSDK = class {
|
|
13179
13465
|
// 3.5 minutes (PING_INTERVAL + 30s grace)
|
|
@@ -13214,7 +13500,7 @@ var SuperatomSDK = class {
|
|
|
13214
13500
|
if (config.queryCacheTTL !== void 0) {
|
|
13215
13501
|
queryCache.setTTL(config.queryCacheTTL);
|
|
13216
13502
|
}
|
|
13217
|
-
logger.info(`Initializing Superatom SDK
|
|
13503
|
+
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`);
|
|
13218
13504
|
this.userManager = new UserManager(this.projectId, 5e3);
|
|
13219
13505
|
this.dashboardManager = new DashboardManager(this.projectId);
|
|
13220
13506
|
this.reportManager = new ReportManager(this.projectId);
|
|
@@ -13268,7 +13554,6 @@ var SuperatomSDK = class {
|
|
|
13268
13554
|
*/
|
|
13269
13555
|
initializeDashboardManager() {
|
|
13270
13556
|
setDashboardManager(this.dashboardManager);
|
|
13271
|
-
logger.info(`DashboardManager initialized for project: ${this.projectId}`);
|
|
13272
13557
|
}
|
|
13273
13558
|
/**
|
|
13274
13559
|
* Get the DashboardManager instance for this SDK
|
|
@@ -13281,7 +13566,6 @@ var SuperatomSDK = class {
|
|
|
13281
13566
|
*/
|
|
13282
13567
|
initializeReportManager() {
|
|
13283
13568
|
setReportManager(this.reportManager);
|
|
13284
|
-
logger.info(`ReportManager initialized for project: ${this.projectId}`);
|
|
13285
13569
|
}
|
|
13286
13570
|
/**
|
|
13287
13571
|
* Get the ReportManager instance for this SDK
|
|
@@ -13658,7 +13942,6 @@ export {
|
|
|
13658
13942
|
CONTEXT_CONFIG,
|
|
13659
13943
|
CleanupService,
|
|
13660
13944
|
LLM,
|
|
13661
|
-
SDK_VERSION,
|
|
13662
13945
|
STORAGE_CONFIG,
|
|
13663
13946
|
SuperatomSDK,
|
|
13664
13947
|
Thread,
|