@superatomai/sdk-node 0.0.4-mds → 0.0.5-mds
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.js +811 -258
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +811 -258
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4029,6 +4029,38 @@ var PromptLoader = class {
|
|
|
4029
4029
|
logger.warn(`Using default database rules for '${this.databaseType}' (file not found at ${rulesPath})`);
|
|
4030
4030
|
return defaultRules;
|
|
4031
4031
|
}
|
|
4032
|
+
/**
|
|
4033
|
+
* Load database-specific SQL rules for a given source type.
|
|
4034
|
+
* Used by the agent architecture where multiple source types can coexist.
|
|
4035
|
+
* @param sourceType - Source type from tool ID (e.g., 'postgres', 'mssql', 'mysql')
|
|
4036
|
+
* @returns Database rules as a string
|
|
4037
|
+
*/
|
|
4038
|
+
async loadDatabaseRulesForType(sourceType) {
|
|
4039
|
+
const typeMap = {
|
|
4040
|
+
"postgres": "postgresql",
|
|
4041
|
+
"postgresql": "postgresql",
|
|
4042
|
+
"mssql": "mssql",
|
|
4043
|
+
"mysql": "postgresql"
|
|
4044
|
+
// MySQL uses similar rules to PostgreSQL
|
|
4045
|
+
};
|
|
4046
|
+
const dbType = typeMap[sourceType] || "postgresql";
|
|
4047
|
+
if (this.databaseRulesCache.has(dbType)) {
|
|
4048
|
+
return this.databaseRulesCache.get(dbType);
|
|
4049
|
+
}
|
|
4050
|
+
const rulesPath = path2.join(this.promptsDir, "database-rules", `${dbType}.md`);
|
|
4051
|
+
try {
|
|
4052
|
+
if (fs3.existsSync(rulesPath)) {
|
|
4053
|
+
const rules = fs3.readFileSync(rulesPath, "utf-8");
|
|
4054
|
+
this.databaseRulesCache.set(dbType, rules);
|
|
4055
|
+
return rules;
|
|
4056
|
+
}
|
|
4057
|
+
} catch (error) {
|
|
4058
|
+
logger.warn(`Could not load database rules for '${dbType}' from file system: ${error}`);
|
|
4059
|
+
}
|
|
4060
|
+
const defaultRules = this.getDefaultDatabaseRulesForType(dbType);
|
|
4061
|
+
this.databaseRulesCache.set(dbType, defaultRules);
|
|
4062
|
+
return defaultRules;
|
|
4063
|
+
}
|
|
4032
4064
|
/**
|
|
4033
4065
|
* Get default database rules as fallback
|
|
4034
4066
|
* @returns Minimal database rules
|
|
@@ -4048,6 +4080,33 @@ var PromptLoader = class {
|
|
|
4048
4080
|
}
|
|
4049
4081
|
return `**Database Type: PostgreSQL**
|
|
4050
4082
|
|
|
4083
|
+
**SQL Query Rules:**
|
|
4084
|
+
- Use \`LIMIT N\` for row limiting (e.g., \`SELECT * FROM table LIMIT 32\`)
|
|
4085
|
+
- Use \`true\` / \`false\` for boolean values
|
|
4086
|
+
- Use \`||\` for string concatenation
|
|
4087
|
+
- Use \`NOW()\` for current timestamp
|
|
4088
|
+
- Use \`::TYPE\` or \`CAST()\` for type casting
|
|
4089
|
+
- Use \`RETURNING\` clause for mutations
|
|
4090
|
+
- NULL values: Use \`NULL\` keyword without quotes`;
|
|
4091
|
+
}
|
|
4092
|
+
/**
|
|
4093
|
+
* Get default database rules for a specific type
|
|
4094
|
+
*/
|
|
4095
|
+
getDefaultDatabaseRulesForType(dbType) {
|
|
4096
|
+
if (dbType === "mssql") {
|
|
4097
|
+
return `**Database Type: Microsoft SQL Server**
|
|
4098
|
+
|
|
4099
|
+
**SQL Query Rules:**
|
|
4100
|
+
- Use \`TOP N\` for row limiting (e.g., \`SELECT TOP 32 * FROM table\`)
|
|
4101
|
+
- Use \`1\` for true, \`0\` for false (no native boolean)
|
|
4102
|
+
- Use \`+\` or \`CONCAT()\` for string concatenation
|
|
4103
|
+
- Use \`GETDATE()\` for current timestamp
|
|
4104
|
+
- Use \`CAST()\` or \`CONVERT()\` for type casting
|
|
4105
|
+
- Use \`OUTPUT INSERTED.*\` instead of \`RETURNING\`
|
|
4106
|
+
- NULL values: Use \`NULL\` keyword without quotes`;
|
|
4107
|
+
}
|
|
4108
|
+
return `**Database Type: PostgreSQL**
|
|
4109
|
+
|
|
4051
4110
|
**SQL Query Rules:**
|
|
4052
4111
|
- Use \`LIMIT N\` for row limiting (e.g., \`SELECT * FROM table LIMIT 32\`)
|
|
4053
4112
|
- Use \`true\` / \`false\` for boolean values
|
|
@@ -6613,9 +6672,11 @@ var SourceAgent = class {
|
|
|
6613
6672
|
const { intent, aggregation = "raw" } = input;
|
|
6614
6673
|
logger.info(`[SourceAgent:${this.tool.name}] Starting | intent: "${intent}" | aggregation: ${aggregation}`);
|
|
6615
6674
|
if (this.streamBuffer.hasCallback()) {
|
|
6675
|
+
const sourceType = this.extractSourceType();
|
|
6676
|
+
const sourceLabel = sourceType !== "unknown" ? ` (${sourceType})` : "";
|
|
6616
6677
|
this.streamBuffer.write(`
|
|
6617
6678
|
|
|
6618
|
-
\u{1F517} **
|
|
6679
|
+
\u{1F517} **Connecting to ${this.tool.name}**${sourceLabel}
|
|
6619
6680
|
|
|
6620
6681
|
`);
|
|
6621
6682
|
await streamDelay();
|
|
@@ -6635,7 +6696,7 @@ var SourceAgent = class {
|
|
|
6635
6696
|
if (this.attempts > 1 && this.streamBuffer.hasCallback()) {
|
|
6636
6697
|
this.streamBuffer.write(`
|
|
6637
6698
|
|
|
6638
|
-
\u{1F504} **
|
|
6699
|
+
\u{1F504} **Query failed, retrying with corrected query** (attempt ${this.attempts}/${this.config.maxRetries})
|
|
6639
6700
|
|
|
6640
6701
|
`);
|
|
6641
6702
|
await streamDelay();
|
|
@@ -6645,10 +6706,29 @@ var SourceAgent = class {
|
|
|
6645
6706
|
cappedInput.limit = this.config.maxRowsPerSource;
|
|
6646
6707
|
}
|
|
6647
6708
|
queryExecuted = cappedInput.sql || cappedInput.query || JSON.stringify(cappedInput);
|
|
6709
|
+
if (this.streamBuffer.hasCallback() && queryExecuted) {
|
|
6710
|
+
const queryDisplay = cappedInput.sql || cappedInput.query;
|
|
6711
|
+
if (queryDisplay) {
|
|
6712
|
+
this.streamBuffer.write(`\u{1F4DD} **Generated SQL query:**
|
|
6713
|
+
\`\`\`sql
|
|
6714
|
+
${queryDisplay}
|
|
6715
|
+
\`\`\`
|
|
6716
|
+
|
|
6717
|
+
`);
|
|
6718
|
+
} else {
|
|
6719
|
+
this.streamBuffer.write(`\u{1F4DD} **Query parameters:**
|
|
6720
|
+
\`\`\`json
|
|
6721
|
+
${JSON.stringify(cappedInput, null, 2)}
|
|
6722
|
+
\`\`\`
|
|
6723
|
+
|
|
6724
|
+
`);
|
|
6725
|
+
}
|
|
6726
|
+
await streamDelay();
|
|
6727
|
+
}
|
|
6648
6728
|
try {
|
|
6649
6729
|
const result = await withProgressHeartbeat(
|
|
6650
6730
|
() => this.tool.fn(cappedInput),
|
|
6651
|
-
`
|
|
6731
|
+
`Executing query`,
|
|
6652
6732
|
this.streamBuffer
|
|
6653
6733
|
);
|
|
6654
6734
|
if (result && result.error) {
|
|
@@ -6660,6 +6740,12 @@ Analyze the error and try again with a corrected query.`;
|
|
|
6660
6740
|
}
|
|
6661
6741
|
resultData = result.data || [];
|
|
6662
6742
|
totalRowsMatched = result.metadata?.totalCount || result.count || resultData.length;
|
|
6743
|
+
if (this.streamBuffer.hasCallback()) {
|
|
6744
|
+
const totalInfo = totalRowsMatched > resultData.length ? ` (${totalRowsMatched} total matched)` : "";
|
|
6745
|
+
this.streamBuffer.write(`\u2705 Retrieved ${resultData.length} rows${totalInfo}
|
|
6746
|
+
|
|
6747
|
+
`);
|
|
6748
|
+
}
|
|
6663
6749
|
const formattedResult = formatToolResultForLLM(result, {
|
|
6664
6750
|
toolName: this.tool.name,
|
|
6665
6751
|
maxRows: 5,
|
|
@@ -6675,7 +6761,9 @@ Analyze the error and try again with a corrected query.`;
|
|
|
6675
6761
|
_metadata: result.metadata,
|
|
6676
6762
|
_sampleData: resultData.slice(0, 3)
|
|
6677
6763
|
},
|
|
6678
|
-
outputSchema: this.tool.outputSchema
|
|
6764
|
+
outputSchema: this.tool.outputSchema,
|
|
6765
|
+
sourceSchema: this.tool.description,
|
|
6766
|
+
sourceType: this.extractSourceType()
|
|
6679
6767
|
};
|
|
6680
6768
|
const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
|
|
6681
6769
|
return `\u2705 Query executed successfully. ${resultData.length} rows returned (${totalRowsMatched} total matched). Data is ready \u2014 do NOT call the tool again.
|
|
@@ -7028,158 +7116,134 @@ var DEFAULT_AGENT_CONFIG = {
|
|
|
7028
7116
|
maxIterations: 10
|
|
7029
7117
|
};
|
|
7030
7118
|
|
|
7031
|
-
// src/userResponse/
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
import path5 from "path";
|
|
7036
|
-
import fs6 from "fs";
|
|
7037
|
-
var Schema = class {
|
|
7038
|
-
constructor(schemaFilePath) {
|
|
7039
|
-
this.cachedSchema = null;
|
|
7040
|
-
this.schemaFilePath = schemaFilePath || path5.join(process.cwd(), "../analysis/data/schema.json");
|
|
7119
|
+
// src/userResponse/utils/component-props-processor.ts
|
|
7120
|
+
function validateExternalTool(externalTool, executedTools, providerName) {
|
|
7121
|
+
if (!externalTool) {
|
|
7122
|
+
return { valid: true };
|
|
7041
7123
|
}
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
const dir = path5.dirname(this.schemaFilePath);
|
|
7049
|
-
if (!fs6.existsSync(dir)) {
|
|
7050
|
-
logger.info(`Creating directory structure: ${dir}`);
|
|
7051
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
7052
|
-
}
|
|
7053
|
-
if (!fs6.existsSync(this.schemaFilePath)) {
|
|
7054
|
-
logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
|
|
7055
|
-
const initialSchema = {
|
|
7056
|
-
database: "",
|
|
7057
|
-
schema: "",
|
|
7058
|
-
description: "",
|
|
7059
|
-
tables: [],
|
|
7060
|
-
relationships: []
|
|
7061
|
-
};
|
|
7062
|
-
fs6.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
|
|
7063
|
-
this.cachedSchema = initialSchema;
|
|
7064
|
-
return initialSchema;
|
|
7065
|
-
}
|
|
7066
|
-
const fileContent = fs6.readFileSync(this.schemaFilePath, "utf-8");
|
|
7067
|
-
const schema2 = JSON.parse(fileContent);
|
|
7068
|
-
this.cachedSchema = schema2;
|
|
7069
|
-
return schema2;
|
|
7070
|
-
} catch (error) {
|
|
7071
|
-
logger.error("Error parsing schema file:", error);
|
|
7072
|
-
return null;
|
|
7073
|
-
}
|
|
7124
|
+
const toolId = externalTool.toolId;
|
|
7125
|
+
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
7126
|
+
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
7127
|
+
if (!isValidTool) {
|
|
7128
|
+
logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
7129
|
+
return { valid: false };
|
|
7074
7130
|
}
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
return this.cachedSchema;
|
|
7082
|
-
}
|
|
7083
|
-
return this.getDatabaseSchema();
|
|
7131
|
+
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
7132
|
+
return { valid: true, executedTool };
|
|
7133
|
+
}
|
|
7134
|
+
function validateAndCleanQuery(query, config) {
|
|
7135
|
+
if (!query) {
|
|
7136
|
+
return { query: null, wasModified: false };
|
|
7084
7137
|
}
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
}
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
tables.push("=".repeat(80));
|
|
7101
|
-
tables.push("");
|
|
7102
|
-
for (const table of schema2.tables) {
|
|
7103
|
-
const tableInfo = [];
|
|
7104
|
-
tableInfo.push(`TABLE: ${table.fullName}`);
|
|
7105
|
-
tableInfo.push(`Description: ${table.description}`);
|
|
7106
|
-
tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
|
|
7107
|
-
tableInfo.push("");
|
|
7108
|
-
tableInfo.push("Columns:");
|
|
7109
|
-
for (const column of table.columns) {
|
|
7110
|
-
let columnLine = ` - ${column.name}: ${column.type}`;
|
|
7111
|
-
if (column.isPrimaryKey) {
|
|
7112
|
-
columnLine += " (PRIMARY KEY)";
|
|
7113
|
-
}
|
|
7114
|
-
if (column.isForeignKey && column.references) {
|
|
7115
|
-
columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
|
|
7116
|
-
}
|
|
7117
|
-
if (!column.nullable) {
|
|
7118
|
-
columnLine += " NOT NULL";
|
|
7119
|
-
}
|
|
7120
|
-
if (column.description) {
|
|
7121
|
-
columnLine += ` - ${column.description}`;
|
|
7122
|
-
}
|
|
7123
|
-
tableInfo.push(columnLine);
|
|
7124
|
-
if (column.sampleValues && column.sampleValues.length > 0) {
|
|
7125
|
-
tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
|
|
7126
|
-
}
|
|
7127
|
-
if (column.statistics) {
|
|
7128
|
-
const stats = column.statistics;
|
|
7129
|
-
if (stats.min !== void 0 && stats.max !== void 0) {
|
|
7130
|
-
tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
|
|
7131
|
-
}
|
|
7132
|
-
if (stats.distinct !== void 0) {
|
|
7133
|
-
tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
|
|
7134
|
-
}
|
|
7135
|
-
}
|
|
7136
|
-
}
|
|
7137
|
-
tableInfo.push("");
|
|
7138
|
-
tables.push(tableInfo.join("\n"));
|
|
7138
|
+
let wasModified = false;
|
|
7139
|
+
let cleanedQuery = query;
|
|
7140
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
7141
|
+
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
7142
|
+
logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
7143
|
+
return { query: null, wasModified: true };
|
|
7144
|
+
}
|
|
7145
|
+
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
7146
|
+
if (fixed) {
|
|
7147
|
+
logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
7148
|
+
wasModified = true;
|
|
7149
|
+
if (typeof cleanedQuery === "string") {
|
|
7150
|
+
cleanedQuery = fixedQuery;
|
|
7151
|
+
} else if (cleanedQuery?.sql) {
|
|
7152
|
+
cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
|
|
7139
7153
|
}
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7154
|
+
}
|
|
7155
|
+
if (typeof cleanedQuery === "string") {
|
|
7156
|
+
const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7157
|
+
if (limitedQuery !== cleanedQuery) wasModified = true;
|
|
7158
|
+
cleanedQuery = limitedQuery;
|
|
7159
|
+
} else if (cleanedQuery?.sql) {
|
|
7160
|
+
const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7161
|
+
if (limitedSql !== cleanedQuery.sql) wasModified = true;
|
|
7162
|
+
cleanedQuery = { ...cleanedQuery, sql: limitedSql };
|
|
7163
|
+
}
|
|
7164
|
+
return { query: cleanedQuery, wasModified };
|
|
7165
|
+
}
|
|
7166
|
+
function processComponentProps(props, executedTools, config) {
|
|
7167
|
+
let cleanedProps = { ...props };
|
|
7168
|
+
if (cleanedProps.externalTool) {
|
|
7169
|
+
const { valid } = validateExternalTool(
|
|
7170
|
+
cleanedProps.externalTool,
|
|
7171
|
+
executedTools,
|
|
7172
|
+
config.providerName
|
|
7173
|
+
);
|
|
7174
|
+
if (!valid) {
|
|
7175
|
+
cleanedProps.externalTool = null;
|
|
7146
7176
|
}
|
|
7147
|
-
return tables.join("\n");
|
|
7148
7177
|
}
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
clearCache() {
|
|
7153
|
-
this.cachedSchema = null;
|
|
7178
|
+
if (cleanedProps.query) {
|
|
7179
|
+
const { query } = validateAndCleanQuery(cleanedProps.query, config);
|
|
7180
|
+
cleanedProps.query = query;
|
|
7154
7181
|
}
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
* @param filePath - Path to the schema file
|
|
7158
|
-
*/
|
|
7159
|
-
setSchemaPath(filePath) {
|
|
7160
|
-
this.schemaFilePath = filePath;
|
|
7161
|
-
this.clearCache();
|
|
7182
|
+
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
7183
|
+
logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
7162
7184
|
}
|
|
7163
|
-
|
|
7164
|
-
|
|
7185
|
+
return cleanedProps;
|
|
7186
|
+
}
|
|
7165
7187
|
|
|
7166
|
-
// src/userResponse/
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7188
|
+
// src/userResponse/prompt-extractor.ts
|
|
7189
|
+
function extractPromptText(content) {
|
|
7190
|
+
if (content === null || content === void 0) {
|
|
7191
|
+
return "";
|
|
7192
|
+
}
|
|
7193
|
+
if (typeof content === "string") {
|
|
7194
|
+
return content;
|
|
7195
|
+
}
|
|
7196
|
+
if (Array.isArray(content)) {
|
|
7197
|
+
return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
|
|
7198
|
+
}
|
|
7199
|
+
if (content && typeof content === "object") {
|
|
7200
|
+
return extractObjectText(content);
|
|
7201
|
+
}
|
|
7202
|
+
return String(content);
|
|
7203
|
+
}
|
|
7204
|
+
function extractContentBlockText(item) {
|
|
7205
|
+
if (typeof item === "string") {
|
|
7206
|
+
return item;
|
|
7207
|
+
}
|
|
7208
|
+
if (item && typeof item === "object") {
|
|
7209
|
+
const obj = item;
|
|
7210
|
+
if (typeof obj.text === "string") {
|
|
7211
|
+
return obj.text;
|
|
7212
|
+
}
|
|
7213
|
+
if (typeof obj.content === "string") {
|
|
7214
|
+
return obj.content;
|
|
7215
|
+
}
|
|
7216
|
+
return JSON.stringify(item, null, 2);
|
|
7217
|
+
}
|
|
7218
|
+
return String(item);
|
|
7219
|
+
}
|
|
7220
|
+
function extractObjectText(obj) {
|
|
7221
|
+
if (typeof obj.text === "string") {
|
|
7222
|
+
return obj.text;
|
|
7223
|
+
}
|
|
7224
|
+
if (typeof obj.content === "string") {
|
|
7225
|
+
return obj.content;
|
|
7226
|
+
}
|
|
7227
|
+
return JSON.stringify(obj, null, 2);
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7230
|
+
// src/userResponse/knowledge-base.ts
|
|
7231
|
+
var getKnowledgeBase = async ({
|
|
7232
|
+
prompt,
|
|
7233
|
+
collections,
|
|
7234
|
+
topK = 1
|
|
7235
|
+
}) => {
|
|
7236
|
+
try {
|
|
7237
|
+
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
|
|
7238
|
+
logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
|
|
7239
|
+
return "";
|
|
7240
|
+
}
|
|
7241
|
+
const result = await collections["knowledge-base"]["query"]({
|
|
7242
|
+
prompt,
|
|
7243
|
+
topK
|
|
7244
|
+
});
|
|
7245
|
+
if (!result || !result.content) {
|
|
7246
|
+
logger.warn("[KnowledgeBase] No knowledge base results returned");
|
|
7183
7247
|
return "";
|
|
7184
7248
|
}
|
|
7185
7249
|
logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
|
|
@@ -7287,116 +7351,601 @@ var KB = {
|
|
|
7287
7351
|
};
|
|
7288
7352
|
var knowledge_base_default = KB;
|
|
7289
7353
|
|
|
7290
|
-
// src/userResponse/
|
|
7291
|
-
function
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7354
|
+
// src/userResponse/agents/agent-component-generator.ts
|
|
7355
|
+
async function generateAgentComponents(params) {
|
|
7356
|
+
const startTime = Date.now();
|
|
7357
|
+
const {
|
|
7358
|
+
analysisContent,
|
|
7359
|
+
components,
|
|
7360
|
+
userPrompt,
|
|
7361
|
+
executedTools,
|
|
7362
|
+
collections,
|
|
7363
|
+
apiKey,
|
|
7364
|
+
componentStreamCallback,
|
|
7365
|
+
userId
|
|
7366
|
+
} = params;
|
|
7367
|
+
logger.info(`[AgentComponentGen] Starting | ${executedTools.length} executed tools | ${components.length} available components`);
|
|
7368
|
+
try {
|
|
7369
|
+
const availableComponentsText = formatAvailableComponents(components);
|
|
7370
|
+
const executedToolsText = formatExecutedTools(executedTools);
|
|
7371
|
+
const sourceTypes = [...new Set(executedTools.map((t) => t.sourceType).filter(Boolean))];
|
|
7372
|
+
let databaseRules = "";
|
|
7373
|
+
if (sourceTypes.length > 0) {
|
|
7374
|
+
const rulesArr = await Promise.all(
|
|
7375
|
+
sourceTypes.map((st) => promptLoader.loadDatabaseRulesForType(st))
|
|
7376
|
+
);
|
|
7377
|
+
databaseRules = rulesArr.join("\n\n");
|
|
7378
|
+
} else {
|
|
7379
|
+
databaseRules = await promptLoader.loadDatabaseRules();
|
|
7380
|
+
}
|
|
7381
|
+
let knowledgeBaseContext = "No additional knowledge base context available.";
|
|
7382
|
+
if (collections) {
|
|
7383
|
+
const kbResult = await knowledge_base_default.getAllKnowledgeBase({
|
|
7384
|
+
prompt: userPrompt || analysisContent,
|
|
7385
|
+
collections,
|
|
7386
|
+
userId,
|
|
7387
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
7388
|
+
});
|
|
7389
|
+
knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
|
|
7390
|
+
}
|
|
7391
|
+
const prompts = await promptLoader.loadPrompts("match-text-components", {
|
|
7392
|
+
USER_PROMPT: userPrompt || "",
|
|
7393
|
+
ANALYSIS_CONTENT: analysisContent,
|
|
7394
|
+
AVAILABLE_COMPONENTS: availableComponentsText,
|
|
7395
|
+
SCHEMA_DOC: "Use column names from executed query results in EXECUTED_TOOLS.",
|
|
7396
|
+
DATABASE_RULES: databaseRules,
|
|
7397
|
+
DEFERRED_TOOLS: "No deferred external tools for this request.",
|
|
7398
|
+
EXECUTED_TOOLS: executedToolsText,
|
|
7399
|
+
KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext,
|
|
7400
|
+
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
7401
|
+
});
|
|
7402
|
+
logger.logLLMPrompt("agentComponentGen", "system", extractPromptText(prompts.system));
|
|
7403
|
+
logger.logLLMPrompt("agentComponentGen", "user", `Text Analysis:
|
|
7404
|
+
${analysisContent}
|
|
7405
|
+
|
|
7406
|
+
Executed Tools:
|
|
7407
|
+
${executedToolsText}`);
|
|
7408
|
+
let fullResponseText = "";
|
|
7409
|
+
let answerComponentStreamed = false;
|
|
7410
|
+
const partialCallback = componentStreamCallback ? (chunk) => {
|
|
7411
|
+
fullResponseText += chunk;
|
|
7412
|
+
if (!answerComponentStreamed && componentStreamCallback) {
|
|
7413
|
+
const streamed = tryStreamAnswerComponent(
|
|
7414
|
+
fullResponseText,
|
|
7415
|
+
components,
|
|
7416
|
+
executedTools,
|
|
7417
|
+
collections,
|
|
7418
|
+
apiKey,
|
|
7419
|
+
componentStreamCallback
|
|
7420
|
+
);
|
|
7421
|
+
if (streamed) answerComponentStreamed = true;
|
|
7422
|
+
}
|
|
7423
|
+
} : void 0;
|
|
7424
|
+
const result = await LLM.stream(
|
|
7425
|
+
{ sys: prompts.system, user: prompts.user },
|
|
7426
|
+
{
|
|
7427
|
+
model: "anthropic/claude-haiku-4-5-20251001",
|
|
7428
|
+
maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
|
|
7429
|
+
temperature: 0,
|
|
7430
|
+
apiKey,
|
|
7431
|
+
partial: partialCallback
|
|
7432
|
+
},
|
|
7433
|
+
true
|
|
7434
|
+
// Parse as JSON
|
|
7435
|
+
);
|
|
7436
|
+
const matchedComponents = result.matchedComponents || [];
|
|
7437
|
+
const layoutTitle = result.layoutTitle || "Dashboard";
|
|
7438
|
+
const layoutDescription = result.layoutDescription || "Multi-component dashboard";
|
|
7439
|
+
if (result.hasAnswerComponent && result.answerComponent?.componentId) {
|
|
7440
|
+
const answer = result.answerComponent;
|
|
7441
|
+
const answerSql = answer.props?.externalTool?.parameters?.sql || "";
|
|
7442
|
+
const answerTitle = answer.props?.title || "";
|
|
7443
|
+
const answerType = answer.componentName || "";
|
|
7444
|
+
const isDuplicate = matchedComponents.some((mc) => {
|
|
7445
|
+
const mcSql = mc.props?.externalTool?.parameters?.sql || "";
|
|
7446
|
+
const mcTitle = mc.props?.title || "";
|
|
7447
|
+
const mcType = mc.componentName || "";
|
|
7448
|
+
return mcType === answerType && (mcTitle === answerTitle || mcSql === answerSql);
|
|
7449
|
+
});
|
|
7450
|
+
if (!isDuplicate) {
|
|
7451
|
+
matchedComponents.unshift(answer);
|
|
7452
|
+
}
|
|
7453
|
+
}
|
|
7454
|
+
logger.info(`[AgentComponentGen] LLM returned ${matchedComponents.length} components`);
|
|
7455
|
+
matchedComponents.forEach((comp, idx) => {
|
|
7456
|
+
logger.info(`[AgentComponentGen] ${idx + 1}. ${comp.componentType} (${comp.componentName})`);
|
|
7457
|
+
});
|
|
7458
|
+
logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
|
|
7459
|
+
const rawActions = result.actions || [];
|
|
7460
|
+
const actions = convertQuestionsToActions(rawActions);
|
|
7461
|
+
const finalComponents = matchedComponents.map((mc) => {
|
|
7462
|
+
const originalComponent = components.find((c) => c.name === mc.componentName);
|
|
7463
|
+
if (!originalComponent) {
|
|
7464
|
+
logger.warn(`[AgentComponentGen] Component "${mc.componentName}" not found in available components`);
|
|
7465
|
+
return null;
|
|
7466
|
+
}
|
|
7467
|
+
const cleanedProps = processComponentProps(
|
|
7468
|
+
mc.props,
|
|
7469
|
+
executedTools,
|
|
7470
|
+
{ providerName: "AgentComponentGen", defaultLimit: 10 }
|
|
7471
|
+
);
|
|
7472
|
+
return {
|
|
7473
|
+
...originalComponent,
|
|
7474
|
+
props: { ...originalComponent.props, ...cleanedProps }
|
|
7475
|
+
};
|
|
7476
|
+
}).filter(Boolean);
|
|
7477
|
+
const validatedComponents = await validateExternalToolQueries(
|
|
7478
|
+
finalComponents,
|
|
7479
|
+
collections,
|
|
7480
|
+
executedTools,
|
|
7481
|
+
apiKey
|
|
7482
|
+
);
|
|
7483
|
+
const elapsed = Date.now() - startTime;
|
|
7484
|
+
logger.info(`[AgentComponentGen] Complete | ${validatedComponents.length}/${finalComponents.length} validated | ${elapsed}ms`);
|
|
7485
|
+
return {
|
|
7486
|
+
components: validatedComponents,
|
|
7487
|
+
layoutTitle,
|
|
7488
|
+
layoutDescription,
|
|
7489
|
+
actions
|
|
7490
|
+
};
|
|
7491
|
+
} catch (error) {
|
|
7492
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7493
|
+
logger.error(`[AgentComponentGen] Error: ${errorMsg}`);
|
|
7494
|
+
return {
|
|
7495
|
+
components: [],
|
|
7496
|
+
layoutTitle: "Dashboard",
|
|
7497
|
+
layoutDescription: "",
|
|
7498
|
+
actions: []
|
|
7499
|
+
};
|
|
7297
7500
|
}
|
|
7298
|
-
|
|
7299
|
-
|
|
7501
|
+
}
|
|
7502
|
+
function formatAvailableComponents(components) {
|
|
7503
|
+
if (!components || components.length === 0) return "No components available";
|
|
7504
|
+
return components.map((comp, idx) => {
|
|
7505
|
+
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
7506
|
+
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
7507
|
+
return `${idx + 1}. ID: ${comp.id}
|
|
7508
|
+
Name: ${comp.name}
|
|
7509
|
+
Type: ${comp.type}
|
|
7510
|
+
Description: ${comp.description || "No description"}
|
|
7511
|
+
Keywords: ${keywords}
|
|
7512
|
+
Props Structure: ${propsPreview}`;
|
|
7513
|
+
}).join("\n\n");
|
|
7514
|
+
}
|
|
7515
|
+
function formatExecutedTools(executedTools) {
|
|
7516
|
+
if (!executedTools || executedTools.length === 0) {
|
|
7517
|
+
return "No external tools were executed for data fetching.";
|
|
7518
|
+
}
|
|
7519
|
+
return "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
|
|
7520
|
+
let outputSchemaText = "Not available";
|
|
7521
|
+
let fieldNamesList = "";
|
|
7522
|
+
const recordCount = tool.result?._totalRecords ?? "unknown";
|
|
7523
|
+
let metadataText = "";
|
|
7524
|
+
if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
|
|
7525
|
+
const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
7526
|
+
metadataText = `
|
|
7527
|
+
\u{1F4CB} METADATA: ${metadataEntries}`;
|
|
7528
|
+
}
|
|
7529
|
+
if (tool.outputSchema) {
|
|
7530
|
+
const fields = tool.outputSchema.fields || [];
|
|
7531
|
+
const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
|
|
7532
|
+
const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
|
|
7533
|
+
fieldNamesList = `
|
|
7534
|
+
\u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
|
|
7535
|
+
\u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
|
|
7536
|
+
const fieldsText = fields.map(
|
|
7537
|
+
(f) => ` "${f.name}" (${f.type}): ${f.description}`
|
|
7538
|
+
).join("\n");
|
|
7539
|
+
outputSchemaText = `${tool.outputSchema.description}
|
|
7540
|
+
Fields:
|
|
7541
|
+
${fieldsText}`;
|
|
7542
|
+
}
|
|
7543
|
+
return `${idx + 1}. **${tool.name}**
|
|
7544
|
+
toolId: "${tool.id}"
|
|
7545
|
+
toolName: "${tool.name}"
|
|
7546
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7547
|
+
recordCount: ${recordCount} rows returned${metadataText}
|
|
7548
|
+
outputSchema: ${outputSchemaText}${fieldNamesList}`;
|
|
7549
|
+
}).join("\n\n");
|
|
7550
|
+
}
|
|
7551
|
+
function tryStreamAnswerComponent(text, components, executedTools, collections, apiKey, callback) {
|
|
7552
|
+
const hasMatch = text.match(/"hasAnswerComponent"\s*:\s*(true|false)/);
|
|
7553
|
+
if (!hasMatch || hasMatch[1] !== "true") return false;
|
|
7554
|
+
const startMatch = text.match(/"answerComponent"\s*:\s*\{/);
|
|
7555
|
+
if (!startMatch) return false;
|
|
7556
|
+
const startPos = startMatch.index + startMatch[0].length - 1;
|
|
7557
|
+
let depth = 0;
|
|
7558
|
+
let inString = false;
|
|
7559
|
+
let escapeNext = false;
|
|
7560
|
+
let endPos = -1;
|
|
7561
|
+
for (let i = startPos; i < text.length; i++) {
|
|
7562
|
+
const char = text[i];
|
|
7563
|
+
if (escapeNext) {
|
|
7564
|
+
escapeNext = false;
|
|
7565
|
+
continue;
|
|
7566
|
+
}
|
|
7567
|
+
if (char === "\\") {
|
|
7568
|
+
escapeNext = true;
|
|
7569
|
+
continue;
|
|
7570
|
+
}
|
|
7571
|
+
if (char === '"') {
|
|
7572
|
+
inString = !inString;
|
|
7573
|
+
continue;
|
|
7574
|
+
}
|
|
7575
|
+
if (!inString) {
|
|
7576
|
+
if (char === "{") depth++;
|
|
7577
|
+
else if (char === "}") {
|
|
7578
|
+
depth--;
|
|
7579
|
+
if (depth === 0) {
|
|
7580
|
+
endPos = i + 1;
|
|
7581
|
+
break;
|
|
7582
|
+
}
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7300
7585
|
}
|
|
7301
|
-
if (
|
|
7302
|
-
|
|
7586
|
+
if (endPos <= startPos) return false;
|
|
7587
|
+
try {
|
|
7588
|
+
const answerData = JSON.parse(text.substring(startPos, endPos));
|
|
7589
|
+
if (!answerData?.componentId) return false;
|
|
7590
|
+
const original = components.find((c) => c.id === answerData.componentId);
|
|
7591
|
+
if (!original) return false;
|
|
7592
|
+
const answerComponent = {
|
|
7593
|
+
...original,
|
|
7594
|
+
props: { ...original.props, ...answerData.props }
|
|
7595
|
+
};
|
|
7596
|
+
const answerProps = answerComponent.props;
|
|
7597
|
+
const sql = answerProps?.externalTool?.parameters?.sql;
|
|
7598
|
+
if (sql && collections?.["external-tools"]?.["execute"]) {
|
|
7599
|
+
const toolId = answerProps.externalTool.toolId;
|
|
7600
|
+
const toolName = answerProps.externalTool.toolName;
|
|
7601
|
+
(async () => {
|
|
7602
|
+
try {
|
|
7603
|
+
const result = await collections["external-tools"]["execute"]({
|
|
7604
|
+
toolId,
|
|
7605
|
+
toolName,
|
|
7606
|
+
sql,
|
|
7607
|
+
data: {}
|
|
7608
|
+
});
|
|
7609
|
+
if (result?.success === false || result?.error) {
|
|
7610
|
+
logger.warn(`[AgentComponentGen] Answer component query failed: ${result?.error}`);
|
|
7611
|
+
return;
|
|
7612
|
+
}
|
|
7613
|
+
queryCache.set(sql, result?.data ?? result);
|
|
7614
|
+
callback(answerComponent);
|
|
7615
|
+
} catch (err) {
|
|
7616
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7617
|
+
logger.warn(`[AgentComponentGen] Answer component validation failed: ${msg}`);
|
|
7618
|
+
}
|
|
7619
|
+
})();
|
|
7620
|
+
} else {
|
|
7621
|
+
callback(answerComponent);
|
|
7622
|
+
}
|
|
7623
|
+
return true;
|
|
7624
|
+
} catch {
|
|
7625
|
+
return false;
|
|
7303
7626
|
}
|
|
7304
|
-
return String(content);
|
|
7305
7627
|
}
|
|
7306
|
-
function
|
|
7307
|
-
if (
|
|
7308
|
-
|
|
7628
|
+
async function validateExternalToolQueries(components, collections, executedTools, apiKey) {
|
|
7629
|
+
if (!collections?.["external-tools"]?.["execute"]) {
|
|
7630
|
+
logger.warn(`[AgentComponentGen] external-tools.execute not available, skipping validation`);
|
|
7631
|
+
return components;
|
|
7309
7632
|
}
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7633
|
+
const validated = [];
|
|
7634
|
+
const withSql = [];
|
|
7635
|
+
const withoutSql = [];
|
|
7636
|
+
for (const comp of components) {
|
|
7637
|
+
const sql = comp.props?.externalTool?.parameters?.sql;
|
|
7638
|
+
if (sql) {
|
|
7639
|
+
withSql.push(comp);
|
|
7640
|
+
} else {
|
|
7641
|
+
withoutSql.push(comp);
|
|
7314
7642
|
}
|
|
7315
|
-
|
|
7316
|
-
|
|
7643
|
+
}
|
|
7644
|
+
validated.push(...withoutSql);
|
|
7645
|
+
if (withSql.length === 0) return validated;
|
|
7646
|
+
const sqlGroups = /* @__PURE__ */ new Map();
|
|
7647
|
+
for (const comp of withSql) {
|
|
7648
|
+
const sql = comp.props.externalTool.parameters.sql;
|
|
7649
|
+
const normalized = sql.replace(/\s+/g, " ").trim();
|
|
7650
|
+
if (!sqlGroups.has(normalized)) {
|
|
7651
|
+
sqlGroups.set(normalized, []);
|
|
7317
7652
|
}
|
|
7318
|
-
|
|
7653
|
+
sqlGroups.get(normalized).push(comp);
|
|
7319
7654
|
}
|
|
7320
|
-
|
|
7321
|
-
}
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7655
|
+
const uniqueQueries = Array.from(sqlGroups.entries());
|
|
7656
|
+
logger.info(`[AgentComponentGen] Validating ${uniqueQueries.length} unique queries (${withSql.length} components)...`);
|
|
7657
|
+
const results = await Promise.allSettled(
|
|
7658
|
+
uniqueQueries.map(([_, comps]) => validateSingleExternalToolQuery(comps[0], collections, executedTools, apiKey))
|
|
7659
|
+
);
|
|
7660
|
+
for (let i = 0; i < results.length; i++) {
|
|
7661
|
+
const result = results[i];
|
|
7662
|
+
const [_, groupComps] = uniqueQueries[i];
|
|
7663
|
+
if (result.status === "fulfilled" && result.value) {
|
|
7664
|
+
validated.push(result.value);
|
|
7665
|
+
for (let j = 1; j < groupComps.length; j++) {
|
|
7666
|
+
const fixedSql = result.value.props?.externalTool?.parameters?.sql;
|
|
7667
|
+
if (fixedSql) {
|
|
7668
|
+
const siblingProps = groupComps[j].props;
|
|
7669
|
+
validated.push({
|
|
7670
|
+
...groupComps[j],
|
|
7671
|
+
props: {
|
|
7672
|
+
...groupComps[j].props,
|
|
7673
|
+
externalTool: {
|
|
7674
|
+
...siblingProps.externalTool,
|
|
7675
|
+
parameters: { ...siblingProps.externalTool.parameters, sql: fixedSql }
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
});
|
|
7679
|
+
} else {
|
|
7680
|
+
validated.push(groupComps[j]);
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
} else {
|
|
7684
|
+
const reason = result.status === "rejected" ? result.reason : "validation failed";
|
|
7685
|
+
for (const comp of groupComps) {
|
|
7686
|
+
logger.warn(`[AgentComponentGen] Excluded ${comp.name}: ${reason}`);
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7325
7689
|
}
|
|
7326
|
-
|
|
7327
|
-
|
|
7690
|
+
logger.info(`[AgentComponentGen] Validation complete: ${validated.length}/${components.length} components passed`);
|
|
7691
|
+
return validated;
|
|
7692
|
+
}
|
|
7693
|
+
async function validateSingleExternalToolQuery(component, collections, executedTools, apiKey) {
|
|
7694
|
+
const compProps = component.props;
|
|
7695
|
+
const toolId = compProps?.externalTool?.toolId;
|
|
7696
|
+
const toolName = compProps?.externalTool?.toolName;
|
|
7697
|
+
let currentSql = compProps?.externalTool?.parameters?.sql;
|
|
7698
|
+
if (!toolId || !currentSql) return component;
|
|
7699
|
+
currentSql = ensureQueryLimit(currentSql, 10, MAX_COMPONENT_QUERY_LIMIT);
|
|
7700
|
+
let attempts = 0;
|
|
7701
|
+
while (attempts < MAX_QUERY_VALIDATION_RETRIES) {
|
|
7702
|
+
attempts++;
|
|
7703
|
+
try {
|
|
7704
|
+
logger.info(`[AgentComponentGen] Validating ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES})`);
|
|
7705
|
+
const result = await collections["external-tools"]["execute"]({
|
|
7706
|
+
toolId,
|
|
7707
|
+
toolName,
|
|
7708
|
+
sql: currentSql,
|
|
7709
|
+
data: {}
|
|
7710
|
+
});
|
|
7711
|
+
if (result?.success === false || result?.error) {
|
|
7712
|
+
const errorMsg = result?.error || "Unknown error";
|
|
7713
|
+
throw new Error(typeof errorMsg === "string" ? errorMsg : JSON.stringify(errorMsg));
|
|
7714
|
+
}
|
|
7715
|
+
const rawToolData = result?.data ?? result;
|
|
7716
|
+
queryCache.set(currentSql, rawToolData);
|
|
7717
|
+
logger.info(`[AgentComponentGen] \u2713 ${component.name} validated (attempt ${attempts})`);
|
|
7718
|
+
return {
|
|
7719
|
+
...component,
|
|
7720
|
+
props: {
|
|
7721
|
+
...component.props,
|
|
7722
|
+
externalTool: {
|
|
7723
|
+
...compProps.externalTool,
|
|
7724
|
+
parameters: {
|
|
7725
|
+
...compProps.externalTool.parameters,
|
|
7726
|
+
sql: currentSql
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
};
|
|
7731
|
+
} catch (error) {
|
|
7732
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7733
|
+
logger.warn(`[AgentComponentGen] \u2717 ${component.name} failed (attempt ${attempts}): ${errorMsg}`);
|
|
7734
|
+
if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
|
|
7735
|
+
logger.error(`[AgentComponentGen] Max retries reached for ${component.name}, excluding`);
|
|
7736
|
+
return null;
|
|
7737
|
+
}
|
|
7738
|
+
try {
|
|
7739
|
+
const fixedSql = await requestExternalToolQueryFix(
|
|
7740
|
+
currentSql,
|
|
7741
|
+
errorMsg,
|
|
7742
|
+
component,
|
|
7743
|
+
executedTools,
|
|
7744
|
+
toolId,
|
|
7745
|
+
apiKey
|
|
7746
|
+
);
|
|
7747
|
+
if (fixedSql && fixedSql !== currentSql) {
|
|
7748
|
+
currentSql = ensureQueryLimit(fixedSql, 10, MAX_COMPONENT_QUERY_LIMIT);
|
|
7749
|
+
logger.info(`[AgentComponentGen] LLM provided fix for ${component.name}, retrying...`);
|
|
7750
|
+
} else {
|
|
7751
|
+
logger.warn(`[AgentComponentGen] LLM returned same or empty query, stopping retries`);
|
|
7752
|
+
return null;
|
|
7753
|
+
}
|
|
7754
|
+
} catch (fixError) {
|
|
7755
|
+
const fixMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
7756
|
+
logger.error(`[AgentComponentGen] Failed to get LLM fix: ${fixMsg}`);
|
|
7757
|
+
return null;
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7328
7760
|
}
|
|
7329
|
-
return
|
|
7761
|
+
return null;
|
|
7330
7762
|
}
|
|
7763
|
+
async function requestExternalToolQueryFix(failedSql, errorMessage, component, executedTools, toolId, apiKey) {
|
|
7764
|
+
const executedTool = executedTools.find((t) => t.id === toolId);
|
|
7765
|
+
const sourceSchema = executedTool?.sourceSchema || "Schema not available";
|
|
7766
|
+
const sourceType = executedTool?.sourceType || "postgresql";
|
|
7767
|
+
const databaseRules = await promptLoader.loadDatabaseRulesForType(sourceType);
|
|
7768
|
+
const prompt = `You are a SQL expert. Fix the following SQL query that failed execution.
|
|
7331
7769
|
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7770
|
+
## Database Schema
|
|
7771
|
+
${sourceSchema}
|
|
7772
|
+
|
|
7773
|
+
## Database-Specific SQL Rules
|
|
7774
|
+
${databaseRules}
|
|
7775
|
+
|
|
7776
|
+
## Component Context
|
|
7777
|
+
- Component Name: ${component.name}
|
|
7778
|
+
- Component Type: ${component.type}
|
|
7779
|
+
- Title: ${component.props?.title || "N/A"}
|
|
7780
|
+
|
|
7781
|
+
## Failed Query
|
|
7782
|
+
\`\`\`sql
|
|
7783
|
+
${failedSql}
|
|
7784
|
+
\`\`\`
|
|
7785
|
+
|
|
7786
|
+
## Error Message
|
|
7787
|
+
${errorMessage}
|
|
7788
|
+
|
|
7789
|
+
## Instructions
|
|
7790
|
+
1. Analyze the error message and identify what caused the query to fail
|
|
7791
|
+
2. Fix the query to resolve the error while preserving the original intent
|
|
7792
|
+
3. Ensure the fixed query follows the database-specific SQL rules above
|
|
7793
|
+
4. Return ONLY the fixed SQL query, no explanations or markdown
|
|
7794
|
+
|
|
7795
|
+
Fixed SQL query:`;
|
|
7796
|
+
const response = await LLM.text(
|
|
7797
|
+
{
|
|
7798
|
+
sys: "You are a SQL expert. Return only the fixed SQL query with no additional text, explanations, or markdown formatting.",
|
|
7799
|
+
user: prompt
|
|
7800
|
+
},
|
|
7801
|
+
{
|
|
7802
|
+
model: "anthropic/claude-haiku-4-5-20251001",
|
|
7803
|
+
maxTokens: 2048,
|
|
7804
|
+
temperature: 0,
|
|
7805
|
+
apiKey
|
|
7806
|
+
}
|
|
7807
|
+
);
|
|
7808
|
+
let fixedQuery = response.trim();
|
|
7809
|
+
fixedQuery = fixedQuery.replace(/^```sql\s*/i, "").replace(/\s*```$/i, "");
|
|
7810
|
+
fixedQuery = fixedQuery.replace(/^```\s*/i, "").replace(/\s*```$/i, "");
|
|
7811
|
+
const { query: validatedQuery } = validateAndFixSqlQuery(fixedQuery);
|
|
7812
|
+
return validatedQuery;
|
|
7346
7813
|
}
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7814
|
+
|
|
7815
|
+
// src/userResponse/anthropic.ts
|
|
7816
|
+
import dotenv from "dotenv";
|
|
7817
|
+
|
|
7818
|
+
// src/userResponse/schema.ts
|
|
7819
|
+
import path5 from "path";
|
|
7820
|
+
import fs6 from "fs";
|
|
7821
|
+
var Schema = class {
|
|
7822
|
+
constructor(schemaFilePath) {
|
|
7823
|
+
this.cachedSchema = null;
|
|
7824
|
+
this.schemaFilePath = schemaFilePath || path5.join(process.cwd(), "../analysis/data/schema.json");
|
|
7357
7825
|
}
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7826
|
+
/**
|
|
7827
|
+
* Gets the database schema from the schema file
|
|
7828
|
+
* @returns Parsed schema object or null if error occurs
|
|
7829
|
+
*/
|
|
7830
|
+
getDatabaseSchema() {
|
|
7831
|
+
try {
|
|
7832
|
+
const dir = path5.dirname(this.schemaFilePath);
|
|
7833
|
+
if (!fs6.existsSync(dir)) {
|
|
7834
|
+
logger.info(`Creating directory structure: ${dir}`);
|
|
7835
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
7836
|
+
}
|
|
7837
|
+
if (!fs6.existsSync(this.schemaFilePath)) {
|
|
7838
|
+
logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
|
|
7839
|
+
const initialSchema = {
|
|
7840
|
+
database: "",
|
|
7841
|
+
schema: "",
|
|
7842
|
+
description: "",
|
|
7843
|
+
tables: [],
|
|
7844
|
+
relationships: []
|
|
7845
|
+
};
|
|
7846
|
+
fs6.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
|
|
7847
|
+
this.cachedSchema = initialSchema;
|
|
7848
|
+
return initialSchema;
|
|
7849
|
+
}
|
|
7850
|
+
const fileContent = fs6.readFileSync(this.schemaFilePath, "utf-8");
|
|
7851
|
+
const schema2 = JSON.parse(fileContent);
|
|
7852
|
+
this.cachedSchema = schema2;
|
|
7853
|
+
return schema2;
|
|
7854
|
+
} catch (error) {
|
|
7855
|
+
logger.error("Error parsing schema file:", error);
|
|
7856
|
+
return null;
|
|
7366
7857
|
}
|
|
7367
7858
|
}
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7859
|
+
/**
|
|
7860
|
+
* Gets the cached schema or loads it if not cached
|
|
7861
|
+
* @returns Cached schema or freshly loaded schema
|
|
7862
|
+
*/
|
|
7863
|
+
getSchema() {
|
|
7864
|
+
if (this.cachedSchema) {
|
|
7865
|
+
return this.cachedSchema;
|
|
7866
|
+
}
|
|
7867
|
+
return this.getDatabaseSchema();
|
|
7376
7868
|
}
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
const
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7869
|
+
/**
|
|
7870
|
+
* Generates database schema documentation for LLM from Snowflake JSON schema
|
|
7871
|
+
* @returns Formatted schema documentation string
|
|
7872
|
+
*/
|
|
7873
|
+
generateSchemaDocumentation() {
|
|
7874
|
+
const schema2 = this.getSchema();
|
|
7875
|
+
if (!schema2) {
|
|
7876
|
+
logger.warn("No database schema found.");
|
|
7877
|
+
return "No database schema available.";
|
|
7878
|
+
}
|
|
7879
|
+
const tables = [];
|
|
7880
|
+
tables.push(`Database: ${schema2.database}`);
|
|
7881
|
+
tables.push(`Schema: ${schema2.schema}`);
|
|
7882
|
+
tables.push(`Description: ${schema2.description}`);
|
|
7883
|
+
tables.push("");
|
|
7884
|
+
tables.push("=".repeat(80));
|
|
7885
|
+
tables.push("");
|
|
7886
|
+
for (const table of schema2.tables) {
|
|
7887
|
+
const tableInfo = [];
|
|
7888
|
+
tableInfo.push(`TABLE: ${table.fullName}`);
|
|
7889
|
+
tableInfo.push(`Description: ${table.description}`);
|
|
7890
|
+
tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
|
|
7891
|
+
tableInfo.push("");
|
|
7892
|
+
tableInfo.push("Columns:");
|
|
7893
|
+
for (const column of table.columns) {
|
|
7894
|
+
let columnLine = ` - ${column.name}: ${column.type}`;
|
|
7895
|
+
if (column.isPrimaryKey) {
|
|
7896
|
+
columnLine += " (PRIMARY KEY)";
|
|
7897
|
+
}
|
|
7898
|
+
if (column.isForeignKey && column.references) {
|
|
7899
|
+
columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
|
|
7900
|
+
}
|
|
7901
|
+
if (!column.nullable) {
|
|
7902
|
+
columnLine += " NOT NULL";
|
|
7903
|
+
}
|
|
7904
|
+
if (column.description) {
|
|
7905
|
+
columnLine += ` - ${column.description}`;
|
|
7906
|
+
}
|
|
7907
|
+
tableInfo.push(columnLine);
|
|
7908
|
+
if (column.sampleValues && column.sampleValues.length > 0) {
|
|
7909
|
+
tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
|
|
7910
|
+
}
|
|
7911
|
+
if (column.statistics) {
|
|
7912
|
+
const stats = column.statistics;
|
|
7913
|
+
if (stats.min !== void 0 && stats.max !== void 0) {
|
|
7914
|
+
tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
|
|
7915
|
+
}
|
|
7916
|
+
if (stats.distinct !== void 0) {
|
|
7917
|
+
tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7921
|
+
tableInfo.push("");
|
|
7922
|
+
tables.push(tableInfo.join("\n"));
|
|
7389
7923
|
}
|
|
7924
|
+
tables.push("=".repeat(80));
|
|
7925
|
+
tables.push("");
|
|
7926
|
+
tables.push("TABLE RELATIONSHIPS:");
|
|
7927
|
+
tables.push("");
|
|
7928
|
+
for (const rel of schema2.relationships) {
|
|
7929
|
+
tables.push(`${rel.from} -> ${rel.to} (${rel.type}): ${rel.keys.join(" = ")}`);
|
|
7930
|
+
}
|
|
7931
|
+
return tables.join("\n");
|
|
7390
7932
|
}
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7933
|
+
/**
|
|
7934
|
+
* Clears the cached schema, forcing a reload on next access
|
|
7935
|
+
*/
|
|
7936
|
+
clearCache() {
|
|
7937
|
+
this.cachedSchema = null;
|
|
7394
7938
|
}
|
|
7395
|
-
|
|
7396
|
-
|
|
7939
|
+
/**
|
|
7940
|
+
* Sets a custom schema file path
|
|
7941
|
+
* @param filePath - Path to the schema file
|
|
7942
|
+
*/
|
|
7943
|
+
setSchemaPath(filePath) {
|
|
7944
|
+
this.schemaFilePath = filePath;
|
|
7945
|
+
this.clearCache();
|
|
7397
7946
|
}
|
|
7398
|
-
|
|
7399
|
-
|
|
7947
|
+
};
|
|
7948
|
+
var schema = new Schema();
|
|
7400
7949
|
|
|
7401
7950
|
// src/userResponse/services/query-execution-service.ts
|
|
7402
7951
|
var QueryExecutionService = class {
|
|
@@ -9329,7 +9878,7 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9329
9878
|
let layoutDescription = "Multi-component dashboard";
|
|
9330
9879
|
let actions = [];
|
|
9331
9880
|
if (!hasExecutedTools) {
|
|
9332
|
-
logger.info(`[AgentFlow] No tools executed \u2014 general question,
|
|
9881
|
+
logger.info(`[AgentFlow] No tools executed \u2014 general question, wrapping in DynamicMarkdownBlock`);
|
|
9333
9882
|
const nextQuestions = await llmInstance.generateNextQuestions(
|
|
9334
9883
|
prompt,
|
|
9335
9884
|
null,
|
|
@@ -9339,6 +9888,18 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9339
9888
|
textResponse
|
|
9340
9889
|
);
|
|
9341
9890
|
actions = convertQuestionsToActions(nextQuestions);
|
|
9891
|
+
const markdownContent = textResponse.replace(/<DashboardComponents>[\s\S]*?<\/DashboardComponents>/g, "").trim();
|
|
9892
|
+
matchedComponents = [{
|
|
9893
|
+
id: "dynamic-markdown-block",
|
|
9894
|
+
name: "DynamicMarkdownBlock",
|
|
9895
|
+
type: "MarkdownBlock",
|
|
9896
|
+
description: "Text response rendered as markdown",
|
|
9897
|
+
props: {
|
|
9898
|
+
content: markdownContent
|
|
9899
|
+
}
|
|
9900
|
+
}];
|
|
9901
|
+
layoutTitle = "";
|
|
9902
|
+
layoutDescription = "";
|
|
9342
9903
|
} else if (components && components.length > 0) {
|
|
9343
9904
|
logger.info(`[AgentFlow] ${agentResponse.executedTools.length} tools executed \u2014 generating components`);
|
|
9344
9905
|
if (streamBuffer.hasCallback()) {
|
|
@@ -9353,18 +9914,16 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9353
9914
|
/<DataTable>[\s\S]*?<\/DataTable>/g,
|
|
9354
9915
|
"<DataTable>[Data preview removed - for table components, REUSE the exact SQL query shown above (the one that returned these results). Do NOT write a new query or embed data in props.]</DataTable>"
|
|
9355
9916
|
);
|
|
9356
|
-
const matchResult = await
|
|
9357
|
-
sanitizedTextResponse,
|
|
9917
|
+
const matchResult = await generateAgentComponents({
|
|
9918
|
+
analysisContent: sanitizedTextResponse,
|
|
9358
9919
|
components,
|
|
9359
|
-
prompt,
|
|
9920
|
+
userPrompt: prompt,
|
|
9921
|
+
executedTools: agentResponse.executedTools,
|
|
9922
|
+
collections,
|
|
9360
9923
|
apiKey,
|
|
9361
9924
|
componentStreamCallback,
|
|
9362
|
-
[],
|
|
9363
|
-
// deferredTools — MainAgent handles tool execution
|
|
9364
|
-
agentResponse.executedTools,
|
|
9365
|
-
collections,
|
|
9366
9925
|
userId
|
|
9367
|
-
);
|
|
9926
|
+
});
|
|
9368
9927
|
matchedComponents = matchResult.components;
|
|
9369
9928
|
layoutTitle = matchResult.layoutTitle;
|
|
9370
9929
|
layoutDescription = matchResult.layoutDescription;
|
|
@@ -9372,9 +9931,11 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9372
9931
|
}
|
|
9373
9932
|
const securedComponents = matchedComponents.map((comp) => {
|
|
9374
9933
|
const props = { ...comp.props };
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
const
|
|
9934
|
+
const sqlValue = props.externalTool?.parameters?.sql || props.externalTool?.parameters?.query;
|
|
9935
|
+
if (sqlValue) {
|
|
9936
|
+
const { sql, query, ...restParams } = props.externalTool.parameters;
|
|
9937
|
+
const cachedData = queryCache.get(sqlValue);
|
|
9938
|
+
const queryId = queryCache.storeQuery(sqlValue, cachedData);
|
|
9378
9939
|
props.externalTool = {
|
|
9379
9940
|
...props.externalTool,
|
|
9380
9941
|
parameters: { queryId, ...restParams }
|
|
@@ -9493,18 +10054,10 @@ async function saveConversation(params) {
|
|
|
9493
10054
|
}
|
|
9494
10055
|
try {
|
|
9495
10056
|
logger.info(`[CONVERSATION_SAVER] Saving conversation for userId: ${userId}, uiBlockId: ${uiBlockId}, threadId: ${threadId}`);
|
|
9496
|
-
const userIdNumber = Number(userId);
|
|
9497
|
-
if (isNaN(userIdNumber)) {
|
|
9498
|
-
logger.warn(`[CONVERSATION_SAVER] Invalid userId: ${userId} (not a valid number)`);
|
|
9499
|
-
return {
|
|
9500
|
-
success: false,
|
|
9501
|
-
error: `Invalid userId: ${userId} (not a valid number)`
|
|
9502
|
-
};
|
|
9503
|
-
}
|
|
9504
10057
|
const dbUIBlock = transformUIBlockForDB(uiblock, userPrompt, uiBlockId);
|
|
9505
10058
|
logger.debug(`[CONVERSATION_SAVER] Transformed UIBlock for DB: ${JSON.stringify(dbUIBlock)}`);
|
|
9506
10059
|
const saveResult = await collections["user-conversations"]["create"]({
|
|
9507
|
-
userId
|
|
10060
|
+
userId,
|
|
9508
10061
|
userPrompt,
|
|
9509
10062
|
uiblock: dbUIBlock,
|
|
9510
10063
|
threadId
|
|
@@ -9525,7 +10078,7 @@ async function saveConversation(params) {
|
|
|
9525
10078
|
userPrompt,
|
|
9526
10079
|
uiBlock: dbUIBlock,
|
|
9527
10080
|
// Use the transformed UIBlock
|
|
9528
|
-
userId
|
|
10081
|
+
userId
|
|
9529
10082
|
});
|
|
9530
10083
|
if (embedResult?.success) {
|
|
9531
10084
|
logger.info("[CONVERSATION_SAVER] Successfully created embedding");
|