@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.js
CHANGED
|
@@ -4086,6 +4086,38 @@ var PromptLoader = class {
|
|
|
4086
4086
|
logger.warn(`Using default database rules for '${this.databaseType}' (file not found at ${rulesPath})`);
|
|
4087
4087
|
return defaultRules;
|
|
4088
4088
|
}
|
|
4089
|
+
/**
|
|
4090
|
+
* Load database-specific SQL rules for a given source type.
|
|
4091
|
+
* Used by the agent architecture where multiple source types can coexist.
|
|
4092
|
+
* @param sourceType - Source type from tool ID (e.g., 'postgres', 'mssql', 'mysql')
|
|
4093
|
+
* @returns Database rules as a string
|
|
4094
|
+
*/
|
|
4095
|
+
async loadDatabaseRulesForType(sourceType) {
|
|
4096
|
+
const typeMap = {
|
|
4097
|
+
"postgres": "postgresql",
|
|
4098
|
+
"postgresql": "postgresql",
|
|
4099
|
+
"mssql": "mssql",
|
|
4100
|
+
"mysql": "postgresql"
|
|
4101
|
+
// MySQL uses similar rules to PostgreSQL
|
|
4102
|
+
};
|
|
4103
|
+
const dbType = typeMap[sourceType] || "postgresql";
|
|
4104
|
+
if (this.databaseRulesCache.has(dbType)) {
|
|
4105
|
+
return this.databaseRulesCache.get(dbType);
|
|
4106
|
+
}
|
|
4107
|
+
const rulesPath = import_path.default.join(this.promptsDir, "database-rules", `${dbType}.md`);
|
|
4108
|
+
try {
|
|
4109
|
+
if (import_fs2.default.existsSync(rulesPath)) {
|
|
4110
|
+
const rules = import_fs2.default.readFileSync(rulesPath, "utf-8");
|
|
4111
|
+
this.databaseRulesCache.set(dbType, rules);
|
|
4112
|
+
return rules;
|
|
4113
|
+
}
|
|
4114
|
+
} catch (error) {
|
|
4115
|
+
logger.warn(`Could not load database rules for '${dbType}' from file system: ${error}`);
|
|
4116
|
+
}
|
|
4117
|
+
const defaultRules = this.getDefaultDatabaseRulesForType(dbType);
|
|
4118
|
+
this.databaseRulesCache.set(dbType, defaultRules);
|
|
4119
|
+
return defaultRules;
|
|
4120
|
+
}
|
|
4089
4121
|
/**
|
|
4090
4122
|
* Get default database rules as fallback
|
|
4091
4123
|
* @returns Minimal database rules
|
|
@@ -4105,6 +4137,33 @@ var PromptLoader = class {
|
|
|
4105
4137
|
}
|
|
4106
4138
|
return `**Database Type: PostgreSQL**
|
|
4107
4139
|
|
|
4140
|
+
**SQL Query Rules:**
|
|
4141
|
+
- Use \`LIMIT N\` for row limiting (e.g., \`SELECT * FROM table LIMIT 32\`)
|
|
4142
|
+
- Use \`true\` / \`false\` for boolean values
|
|
4143
|
+
- Use \`||\` for string concatenation
|
|
4144
|
+
- Use \`NOW()\` for current timestamp
|
|
4145
|
+
- Use \`::TYPE\` or \`CAST()\` for type casting
|
|
4146
|
+
- Use \`RETURNING\` clause for mutations
|
|
4147
|
+
- NULL values: Use \`NULL\` keyword without quotes`;
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* Get default database rules for a specific type
|
|
4151
|
+
*/
|
|
4152
|
+
getDefaultDatabaseRulesForType(dbType) {
|
|
4153
|
+
if (dbType === "mssql") {
|
|
4154
|
+
return `**Database Type: Microsoft SQL Server**
|
|
4155
|
+
|
|
4156
|
+
**SQL Query Rules:**
|
|
4157
|
+
- Use \`TOP N\` for row limiting (e.g., \`SELECT TOP 32 * FROM table\`)
|
|
4158
|
+
- Use \`1\` for true, \`0\` for false (no native boolean)
|
|
4159
|
+
- Use \`+\` or \`CONCAT()\` for string concatenation
|
|
4160
|
+
- Use \`GETDATE()\` for current timestamp
|
|
4161
|
+
- Use \`CAST()\` or \`CONVERT()\` for type casting
|
|
4162
|
+
- Use \`OUTPUT INSERTED.*\` instead of \`RETURNING\`
|
|
4163
|
+
- NULL values: Use \`NULL\` keyword without quotes`;
|
|
4164
|
+
}
|
|
4165
|
+
return `**Database Type: PostgreSQL**
|
|
4166
|
+
|
|
4108
4167
|
**SQL Query Rules:**
|
|
4109
4168
|
- Use \`LIMIT N\` for row limiting (e.g., \`SELECT * FROM table LIMIT 32\`)
|
|
4110
4169
|
- Use \`true\` / \`false\` for boolean values
|
|
@@ -6670,9 +6729,11 @@ var SourceAgent = class {
|
|
|
6670
6729
|
const { intent, aggregation = "raw" } = input;
|
|
6671
6730
|
logger.info(`[SourceAgent:${this.tool.name}] Starting | intent: "${intent}" | aggregation: ${aggregation}`);
|
|
6672
6731
|
if (this.streamBuffer.hasCallback()) {
|
|
6732
|
+
const sourceType = this.extractSourceType();
|
|
6733
|
+
const sourceLabel = sourceType !== "unknown" ? ` (${sourceType})` : "";
|
|
6673
6734
|
this.streamBuffer.write(`
|
|
6674
6735
|
|
|
6675
|
-
\u{1F517} **
|
|
6736
|
+
\u{1F517} **Connecting to ${this.tool.name}**${sourceLabel}
|
|
6676
6737
|
|
|
6677
6738
|
`);
|
|
6678
6739
|
await streamDelay();
|
|
@@ -6692,7 +6753,7 @@ var SourceAgent = class {
|
|
|
6692
6753
|
if (this.attempts > 1 && this.streamBuffer.hasCallback()) {
|
|
6693
6754
|
this.streamBuffer.write(`
|
|
6694
6755
|
|
|
6695
|
-
\u{1F504} **
|
|
6756
|
+
\u{1F504} **Query failed, retrying with corrected query** (attempt ${this.attempts}/${this.config.maxRetries})
|
|
6696
6757
|
|
|
6697
6758
|
`);
|
|
6698
6759
|
await streamDelay();
|
|
@@ -6702,10 +6763,29 @@ var SourceAgent = class {
|
|
|
6702
6763
|
cappedInput.limit = this.config.maxRowsPerSource;
|
|
6703
6764
|
}
|
|
6704
6765
|
queryExecuted = cappedInput.sql || cappedInput.query || JSON.stringify(cappedInput);
|
|
6766
|
+
if (this.streamBuffer.hasCallback() && queryExecuted) {
|
|
6767
|
+
const queryDisplay = cappedInput.sql || cappedInput.query;
|
|
6768
|
+
if (queryDisplay) {
|
|
6769
|
+
this.streamBuffer.write(`\u{1F4DD} **Generated SQL query:**
|
|
6770
|
+
\`\`\`sql
|
|
6771
|
+
${queryDisplay}
|
|
6772
|
+
\`\`\`
|
|
6773
|
+
|
|
6774
|
+
`);
|
|
6775
|
+
} else {
|
|
6776
|
+
this.streamBuffer.write(`\u{1F4DD} **Query parameters:**
|
|
6777
|
+
\`\`\`json
|
|
6778
|
+
${JSON.stringify(cappedInput, null, 2)}
|
|
6779
|
+
\`\`\`
|
|
6780
|
+
|
|
6781
|
+
`);
|
|
6782
|
+
}
|
|
6783
|
+
await streamDelay();
|
|
6784
|
+
}
|
|
6705
6785
|
try {
|
|
6706
6786
|
const result = await withProgressHeartbeat(
|
|
6707
6787
|
() => this.tool.fn(cappedInput),
|
|
6708
|
-
`
|
|
6788
|
+
`Executing query`,
|
|
6709
6789
|
this.streamBuffer
|
|
6710
6790
|
);
|
|
6711
6791
|
if (result && result.error) {
|
|
@@ -6717,6 +6797,12 @@ Analyze the error and try again with a corrected query.`;
|
|
|
6717
6797
|
}
|
|
6718
6798
|
resultData = result.data || [];
|
|
6719
6799
|
totalRowsMatched = result.metadata?.totalCount || result.count || resultData.length;
|
|
6800
|
+
if (this.streamBuffer.hasCallback()) {
|
|
6801
|
+
const totalInfo = totalRowsMatched > resultData.length ? ` (${totalRowsMatched} total matched)` : "";
|
|
6802
|
+
this.streamBuffer.write(`\u2705 Retrieved ${resultData.length} rows${totalInfo}
|
|
6803
|
+
|
|
6804
|
+
`);
|
|
6805
|
+
}
|
|
6720
6806
|
const formattedResult = formatToolResultForLLM(result, {
|
|
6721
6807
|
toolName: this.tool.name,
|
|
6722
6808
|
maxRows: 5,
|
|
@@ -6732,7 +6818,9 @@ Analyze the error and try again with a corrected query.`;
|
|
|
6732
6818
|
_metadata: result.metadata,
|
|
6733
6819
|
_sampleData: resultData.slice(0, 3)
|
|
6734
6820
|
},
|
|
6735
|
-
outputSchema: this.tool.outputSchema
|
|
6821
|
+
outputSchema: this.tool.outputSchema,
|
|
6822
|
+
sourceSchema: this.tool.description,
|
|
6823
|
+
sourceType: this.extractSourceType()
|
|
6736
6824
|
};
|
|
6737
6825
|
const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
|
|
6738
6826
|
return `\u2705 Query executed successfully. ${resultData.length} rows returned (${totalRowsMatched} total matched). Data is ready \u2014 do NOT call the tool again.
|
|
@@ -7085,158 +7173,134 @@ var DEFAULT_AGENT_CONFIG = {
|
|
|
7085
7173
|
maxIterations: 10
|
|
7086
7174
|
};
|
|
7087
7175
|
|
|
7088
|
-
// src/userResponse/
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
var import_path4 = __toESM(require("path"));
|
|
7093
|
-
var import_fs5 = __toESM(require("fs"));
|
|
7094
|
-
var Schema = class {
|
|
7095
|
-
constructor(schemaFilePath) {
|
|
7096
|
-
this.cachedSchema = null;
|
|
7097
|
-
this.schemaFilePath = schemaFilePath || import_path4.default.join(process.cwd(), "../analysis/data/schema.json");
|
|
7176
|
+
// src/userResponse/utils/component-props-processor.ts
|
|
7177
|
+
function validateExternalTool(externalTool, executedTools, providerName) {
|
|
7178
|
+
if (!externalTool) {
|
|
7179
|
+
return { valid: true };
|
|
7098
7180
|
}
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
const dir = import_path4.default.dirname(this.schemaFilePath);
|
|
7106
|
-
if (!import_fs5.default.existsSync(dir)) {
|
|
7107
|
-
logger.info(`Creating directory structure: ${dir}`);
|
|
7108
|
-
import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
7109
|
-
}
|
|
7110
|
-
if (!import_fs5.default.existsSync(this.schemaFilePath)) {
|
|
7111
|
-
logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
|
|
7112
|
-
const initialSchema = {
|
|
7113
|
-
database: "",
|
|
7114
|
-
schema: "",
|
|
7115
|
-
description: "",
|
|
7116
|
-
tables: [],
|
|
7117
|
-
relationships: []
|
|
7118
|
-
};
|
|
7119
|
-
import_fs5.default.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
|
|
7120
|
-
this.cachedSchema = initialSchema;
|
|
7121
|
-
return initialSchema;
|
|
7122
|
-
}
|
|
7123
|
-
const fileContent = import_fs5.default.readFileSync(this.schemaFilePath, "utf-8");
|
|
7124
|
-
const schema2 = JSON.parse(fileContent);
|
|
7125
|
-
this.cachedSchema = schema2;
|
|
7126
|
-
return schema2;
|
|
7127
|
-
} catch (error) {
|
|
7128
|
-
logger.error("Error parsing schema file:", error);
|
|
7129
|
-
return null;
|
|
7130
|
-
}
|
|
7181
|
+
const toolId = externalTool.toolId;
|
|
7182
|
+
const validToolIds = (executedTools || []).map((t) => t.id);
|
|
7183
|
+
const isValidTool = toolId && typeof toolId === "string" && validToolIds.includes(toolId);
|
|
7184
|
+
if (!isValidTool) {
|
|
7185
|
+
logger.warn(`[${providerName}] externalTool.toolId "${toolId}" not found in executed tools [${validToolIds.join(", ")}], setting to null`);
|
|
7186
|
+
return { valid: false };
|
|
7131
7187
|
}
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
return this.cachedSchema;
|
|
7139
|
-
}
|
|
7140
|
-
return this.getDatabaseSchema();
|
|
7188
|
+
const executedTool = executedTools?.find((t) => t.id === toolId);
|
|
7189
|
+
return { valid: true, executedTool };
|
|
7190
|
+
}
|
|
7191
|
+
function validateAndCleanQuery(query, config) {
|
|
7192
|
+
if (!query) {
|
|
7193
|
+
return { query: null, wasModified: false };
|
|
7141
7194
|
}
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
}
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
tables.push("=".repeat(80));
|
|
7158
|
-
tables.push("");
|
|
7159
|
-
for (const table of schema2.tables) {
|
|
7160
|
-
const tableInfo = [];
|
|
7161
|
-
tableInfo.push(`TABLE: ${table.fullName}`);
|
|
7162
|
-
tableInfo.push(`Description: ${table.description}`);
|
|
7163
|
-
tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
|
|
7164
|
-
tableInfo.push("");
|
|
7165
|
-
tableInfo.push("Columns:");
|
|
7166
|
-
for (const column of table.columns) {
|
|
7167
|
-
let columnLine = ` - ${column.name}: ${column.type}`;
|
|
7168
|
-
if (column.isPrimaryKey) {
|
|
7169
|
-
columnLine += " (PRIMARY KEY)";
|
|
7170
|
-
}
|
|
7171
|
-
if (column.isForeignKey && column.references) {
|
|
7172
|
-
columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
|
|
7173
|
-
}
|
|
7174
|
-
if (!column.nullable) {
|
|
7175
|
-
columnLine += " NOT NULL";
|
|
7176
|
-
}
|
|
7177
|
-
if (column.description) {
|
|
7178
|
-
columnLine += ` - ${column.description}`;
|
|
7179
|
-
}
|
|
7180
|
-
tableInfo.push(columnLine);
|
|
7181
|
-
if (column.sampleValues && column.sampleValues.length > 0) {
|
|
7182
|
-
tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
|
|
7183
|
-
}
|
|
7184
|
-
if (column.statistics) {
|
|
7185
|
-
const stats = column.statistics;
|
|
7186
|
-
if (stats.min !== void 0 && stats.max !== void 0) {
|
|
7187
|
-
tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
|
|
7188
|
-
}
|
|
7189
|
-
if (stats.distinct !== void 0) {
|
|
7190
|
-
tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
|
|
7191
|
-
}
|
|
7192
|
-
}
|
|
7193
|
-
}
|
|
7194
|
-
tableInfo.push("");
|
|
7195
|
-
tables.push(tableInfo.join("\n"));
|
|
7195
|
+
let wasModified = false;
|
|
7196
|
+
let cleanedQuery = query;
|
|
7197
|
+
const queryStr = typeof query === "string" ? query : query?.sql || "";
|
|
7198
|
+
if (queryStr.includes("OPENJSON") || queryStr.includes("JSON_VALUE")) {
|
|
7199
|
+
logger.warn(`[${config.providerName}] Query contains OPENJSON/JSON_VALUE (invalid - cannot parse tool result), setting query to null`);
|
|
7200
|
+
return { query: null, wasModified: true };
|
|
7201
|
+
}
|
|
7202
|
+
const { query: fixedQuery, fixed, fixes } = validateAndFixSqlQuery(queryStr);
|
|
7203
|
+
if (fixed) {
|
|
7204
|
+
logger.warn(`[${config.providerName}] SQL fixes applied to component query: ${fixes.join("; ")}`);
|
|
7205
|
+
wasModified = true;
|
|
7206
|
+
if (typeof cleanedQuery === "string") {
|
|
7207
|
+
cleanedQuery = fixedQuery;
|
|
7208
|
+
} else if (cleanedQuery?.sql) {
|
|
7209
|
+
cleanedQuery = { ...cleanedQuery, sql: fixedQuery };
|
|
7196
7210
|
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7211
|
+
}
|
|
7212
|
+
if (typeof cleanedQuery === "string") {
|
|
7213
|
+
const limitedQuery = ensureQueryLimit(cleanedQuery, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7214
|
+
if (limitedQuery !== cleanedQuery) wasModified = true;
|
|
7215
|
+
cleanedQuery = limitedQuery;
|
|
7216
|
+
} else if (cleanedQuery?.sql) {
|
|
7217
|
+
const limitedSql = ensureQueryLimit(cleanedQuery.sql, config.defaultLimit, MAX_COMPONENT_QUERY_LIMIT);
|
|
7218
|
+
if (limitedSql !== cleanedQuery.sql) wasModified = true;
|
|
7219
|
+
cleanedQuery = { ...cleanedQuery, sql: limitedSql };
|
|
7220
|
+
}
|
|
7221
|
+
return { query: cleanedQuery, wasModified };
|
|
7222
|
+
}
|
|
7223
|
+
function processComponentProps(props, executedTools, config) {
|
|
7224
|
+
let cleanedProps = { ...props };
|
|
7225
|
+
if (cleanedProps.externalTool) {
|
|
7226
|
+
const { valid } = validateExternalTool(
|
|
7227
|
+
cleanedProps.externalTool,
|
|
7228
|
+
executedTools,
|
|
7229
|
+
config.providerName
|
|
7230
|
+
);
|
|
7231
|
+
if (!valid) {
|
|
7232
|
+
cleanedProps.externalTool = null;
|
|
7203
7233
|
}
|
|
7204
|
-
return tables.join("\n");
|
|
7205
7234
|
}
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
clearCache() {
|
|
7210
|
-
this.cachedSchema = null;
|
|
7235
|
+
if (cleanedProps.query) {
|
|
7236
|
+
const { query } = validateAndCleanQuery(cleanedProps.query, config);
|
|
7237
|
+
cleanedProps.query = query;
|
|
7211
7238
|
}
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
* @param filePath - Path to the schema file
|
|
7215
|
-
*/
|
|
7216
|
-
setSchemaPath(filePath) {
|
|
7217
|
-
this.schemaFilePath = filePath;
|
|
7218
|
-
this.clearCache();
|
|
7239
|
+
if (cleanedProps.query && cleanedProps.externalTool) {
|
|
7240
|
+
logger.info(`[${config.providerName}] Both query and externalTool exist, keeping both - frontend will decide`);
|
|
7219
7241
|
}
|
|
7220
|
-
|
|
7221
|
-
|
|
7242
|
+
return cleanedProps;
|
|
7243
|
+
}
|
|
7222
7244
|
|
|
7223
|
-
// src/userResponse/
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7245
|
+
// src/userResponse/prompt-extractor.ts
|
|
7246
|
+
function extractPromptText(content) {
|
|
7247
|
+
if (content === null || content === void 0) {
|
|
7248
|
+
return "";
|
|
7249
|
+
}
|
|
7250
|
+
if (typeof content === "string") {
|
|
7251
|
+
return content;
|
|
7252
|
+
}
|
|
7253
|
+
if (Array.isArray(content)) {
|
|
7254
|
+
return content.map((item) => extractContentBlockText(item)).filter((text) => text.length > 0).join("\n\n---\n\n");
|
|
7255
|
+
}
|
|
7256
|
+
if (content && typeof content === "object") {
|
|
7257
|
+
return extractObjectText(content);
|
|
7258
|
+
}
|
|
7259
|
+
return String(content);
|
|
7260
|
+
}
|
|
7261
|
+
function extractContentBlockText(item) {
|
|
7262
|
+
if (typeof item === "string") {
|
|
7263
|
+
return item;
|
|
7264
|
+
}
|
|
7265
|
+
if (item && typeof item === "object") {
|
|
7266
|
+
const obj = item;
|
|
7267
|
+
if (typeof obj.text === "string") {
|
|
7268
|
+
return obj.text;
|
|
7269
|
+
}
|
|
7270
|
+
if (typeof obj.content === "string") {
|
|
7271
|
+
return obj.content;
|
|
7272
|
+
}
|
|
7273
|
+
return JSON.stringify(item, null, 2);
|
|
7274
|
+
}
|
|
7275
|
+
return String(item);
|
|
7276
|
+
}
|
|
7277
|
+
function extractObjectText(obj) {
|
|
7278
|
+
if (typeof obj.text === "string") {
|
|
7279
|
+
return obj.text;
|
|
7280
|
+
}
|
|
7281
|
+
if (typeof obj.content === "string") {
|
|
7282
|
+
return obj.content;
|
|
7283
|
+
}
|
|
7284
|
+
return JSON.stringify(obj, null, 2);
|
|
7285
|
+
}
|
|
7286
|
+
|
|
7287
|
+
// src/userResponse/knowledge-base.ts
|
|
7288
|
+
var getKnowledgeBase = async ({
|
|
7289
|
+
prompt,
|
|
7290
|
+
collections,
|
|
7291
|
+
topK = 1
|
|
7292
|
+
}) => {
|
|
7293
|
+
try {
|
|
7294
|
+
if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
|
|
7295
|
+
logger.warn("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
|
|
7296
|
+
return "";
|
|
7297
|
+
}
|
|
7298
|
+
const result = await collections["knowledge-base"]["query"]({
|
|
7299
|
+
prompt,
|
|
7300
|
+
topK
|
|
7301
|
+
});
|
|
7302
|
+
if (!result || !result.content) {
|
|
7303
|
+
logger.warn("[KnowledgeBase] No knowledge base results returned");
|
|
7240
7304
|
return "";
|
|
7241
7305
|
}
|
|
7242
7306
|
logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
|
|
@@ -7344,116 +7408,601 @@ var KB = {
|
|
|
7344
7408
|
};
|
|
7345
7409
|
var knowledge_base_default = KB;
|
|
7346
7410
|
|
|
7347
|
-
// src/userResponse/
|
|
7348
|
-
function
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7411
|
+
// src/userResponse/agents/agent-component-generator.ts
|
|
7412
|
+
async function generateAgentComponents(params) {
|
|
7413
|
+
const startTime = Date.now();
|
|
7414
|
+
const {
|
|
7415
|
+
analysisContent,
|
|
7416
|
+
components,
|
|
7417
|
+
userPrompt,
|
|
7418
|
+
executedTools,
|
|
7419
|
+
collections,
|
|
7420
|
+
apiKey,
|
|
7421
|
+
componentStreamCallback,
|
|
7422
|
+
userId
|
|
7423
|
+
} = params;
|
|
7424
|
+
logger.info(`[AgentComponentGen] Starting | ${executedTools.length} executed tools | ${components.length} available components`);
|
|
7425
|
+
try {
|
|
7426
|
+
const availableComponentsText = formatAvailableComponents(components);
|
|
7427
|
+
const executedToolsText = formatExecutedTools(executedTools);
|
|
7428
|
+
const sourceTypes = [...new Set(executedTools.map((t) => t.sourceType).filter(Boolean))];
|
|
7429
|
+
let databaseRules = "";
|
|
7430
|
+
if (sourceTypes.length > 0) {
|
|
7431
|
+
const rulesArr = await Promise.all(
|
|
7432
|
+
sourceTypes.map((st) => promptLoader.loadDatabaseRulesForType(st))
|
|
7433
|
+
);
|
|
7434
|
+
databaseRules = rulesArr.join("\n\n");
|
|
7435
|
+
} else {
|
|
7436
|
+
databaseRules = await promptLoader.loadDatabaseRules();
|
|
7437
|
+
}
|
|
7438
|
+
let knowledgeBaseContext = "No additional knowledge base context available.";
|
|
7439
|
+
if (collections) {
|
|
7440
|
+
const kbResult = await knowledge_base_default.getAllKnowledgeBase({
|
|
7441
|
+
prompt: userPrompt || analysisContent,
|
|
7442
|
+
collections,
|
|
7443
|
+
userId,
|
|
7444
|
+
topK: KNOWLEDGE_BASE_TOP_K
|
|
7445
|
+
});
|
|
7446
|
+
knowledgeBaseContext = kbResult.combinedContext || knowledgeBaseContext;
|
|
7447
|
+
}
|
|
7448
|
+
const prompts = await promptLoader.loadPrompts("match-text-components", {
|
|
7449
|
+
USER_PROMPT: userPrompt || "",
|
|
7450
|
+
ANALYSIS_CONTENT: analysisContent,
|
|
7451
|
+
AVAILABLE_COMPONENTS: availableComponentsText,
|
|
7452
|
+
SCHEMA_DOC: "Use column names from executed query results in EXECUTED_TOOLS.",
|
|
7453
|
+
DATABASE_RULES: databaseRules,
|
|
7454
|
+
DEFERRED_TOOLS: "No deferred external tools for this request.",
|
|
7455
|
+
EXECUTED_TOOLS: executedToolsText,
|
|
7456
|
+
KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext,
|
|
7457
|
+
CURRENT_DATETIME: getCurrentDateTimeForPrompt()
|
|
7458
|
+
});
|
|
7459
|
+
logger.logLLMPrompt("agentComponentGen", "system", extractPromptText(prompts.system));
|
|
7460
|
+
logger.logLLMPrompt("agentComponentGen", "user", `Text Analysis:
|
|
7461
|
+
${analysisContent}
|
|
7462
|
+
|
|
7463
|
+
Executed Tools:
|
|
7464
|
+
${executedToolsText}`);
|
|
7465
|
+
let fullResponseText = "";
|
|
7466
|
+
let answerComponentStreamed = false;
|
|
7467
|
+
const partialCallback = componentStreamCallback ? (chunk) => {
|
|
7468
|
+
fullResponseText += chunk;
|
|
7469
|
+
if (!answerComponentStreamed && componentStreamCallback) {
|
|
7470
|
+
const streamed = tryStreamAnswerComponent(
|
|
7471
|
+
fullResponseText,
|
|
7472
|
+
components,
|
|
7473
|
+
executedTools,
|
|
7474
|
+
collections,
|
|
7475
|
+
apiKey,
|
|
7476
|
+
componentStreamCallback
|
|
7477
|
+
);
|
|
7478
|
+
if (streamed) answerComponentStreamed = true;
|
|
7479
|
+
}
|
|
7480
|
+
} : void 0;
|
|
7481
|
+
const result = await LLM.stream(
|
|
7482
|
+
{ sys: prompts.system, user: prompts.user },
|
|
7483
|
+
{
|
|
7484
|
+
model: "anthropic/claude-haiku-4-5-20251001",
|
|
7485
|
+
maxTokens: MAX_TOKENS_COMPONENT_MATCHING,
|
|
7486
|
+
temperature: 0,
|
|
7487
|
+
apiKey,
|
|
7488
|
+
partial: partialCallback
|
|
7489
|
+
},
|
|
7490
|
+
true
|
|
7491
|
+
// Parse as JSON
|
|
7492
|
+
);
|
|
7493
|
+
const matchedComponents = result.matchedComponents || [];
|
|
7494
|
+
const layoutTitle = result.layoutTitle || "Dashboard";
|
|
7495
|
+
const layoutDescription = result.layoutDescription || "Multi-component dashboard";
|
|
7496
|
+
if (result.hasAnswerComponent && result.answerComponent?.componentId) {
|
|
7497
|
+
const answer = result.answerComponent;
|
|
7498
|
+
const answerSql = answer.props?.externalTool?.parameters?.sql || "";
|
|
7499
|
+
const answerTitle = answer.props?.title || "";
|
|
7500
|
+
const answerType = answer.componentName || "";
|
|
7501
|
+
const isDuplicate = matchedComponents.some((mc) => {
|
|
7502
|
+
const mcSql = mc.props?.externalTool?.parameters?.sql || "";
|
|
7503
|
+
const mcTitle = mc.props?.title || "";
|
|
7504
|
+
const mcType = mc.componentName || "";
|
|
7505
|
+
return mcType === answerType && (mcTitle === answerTitle || mcSql === answerSql);
|
|
7506
|
+
});
|
|
7507
|
+
if (!isDuplicate) {
|
|
7508
|
+
matchedComponents.unshift(answer);
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
logger.info(`[AgentComponentGen] LLM returned ${matchedComponents.length} components`);
|
|
7512
|
+
matchedComponents.forEach((comp, idx) => {
|
|
7513
|
+
logger.info(`[AgentComponentGen] ${idx + 1}. ${comp.componentType} (${comp.componentName})`);
|
|
7514
|
+
});
|
|
7515
|
+
logger.file("\n=============================\nFull LLM response:", JSON.stringify(result, null, 2));
|
|
7516
|
+
const rawActions = result.actions || [];
|
|
7517
|
+
const actions = convertQuestionsToActions(rawActions);
|
|
7518
|
+
const finalComponents = matchedComponents.map((mc) => {
|
|
7519
|
+
const originalComponent = components.find((c) => c.name === mc.componentName);
|
|
7520
|
+
if (!originalComponent) {
|
|
7521
|
+
logger.warn(`[AgentComponentGen] Component "${mc.componentName}" not found in available components`);
|
|
7522
|
+
return null;
|
|
7523
|
+
}
|
|
7524
|
+
const cleanedProps = processComponentProps(
|
|
7525
|
+
mc.props,
|
|
7526
|
+
executedTools,
|
|
7527
|
+
{ providerName: "AgentComponentGen", defaultLimit: 10 }
|
|
7528
|
+
);
|
|
7529
|
+
return {
|
|
7530
|
+
...originalComponent,
|
|
7531
|
+
props: { ...originalComponent.props, ...cleanedProps }
|
|
7532
|
+
};
|
|
7533
|
+
}).filter(Boolean);
|
|
7534
|
+
const validatedComponents = await validateExternalToolQueries(
|
|
7535
|
+
finalComponents,
|
|
7536
|
+
collections,
|
|
7537
|
+
executedTools,
|
|
7538
|
+
apiKey
|
|
7539
|
+
);
|
|
7540
|
+
const elapsed = Date.now() - startTime;
|
|
7541
|
+
logger.info(`[AgentComponentGen] Complete | ${validatedComponents.length}/${finalComponents.length} validated | ${elapsed}ms`);
|
|
7542
|
+
return {
|
|
7543
|
+
components: validatedComponents,
|
|
7544
|
+
layoutTitle,
|
|
7545
|
+
layoutDescription,
|
|
7546
|
+
actions
|
|
7547
|
+
};
|
|
7548
|
+
} catch (error) {
|
|
7549
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7550
|
+
logger.error(`[AgentComponentGen] Error: ${errorMsg}`);
|
|
7551
|
+
return {
|
|
7552
|
+
components: [],
|
|
7553
|
+
layoutTitle: "Dashboard",
|
|
7554
|
+
layoutDescription: "",
|
|
7555
|
+
actions: []
|
|
7556
|
+
};
|
|
7354
7557
|
}
|
|
7355
|
-
|
|
7356
|
-
|
|
7558
|
+
}
|
|
7559
|
+
function formatAvailableComponents(components) {
|
|
7560
|
+
if (!components || components.length === 0) return "No components available";
|
|
7561
|
+
return components.map((comp, idx) => {
|
|
7562
|
+
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
7563
|
+
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
7564
|
+
return `${idx + 1}. ID: ${comp.id}
|
|
7565
|
+
Name: ${comp.name}
|
|
7566
|
+
Type: ${comp.type}
|
|
7567
|
+
Description: ${comp.description || "No description"}
|
|
7568
|
+
Keywords: ${keywords}
|
|
7569
|
+
Props Structure: ${propsPreview}`;
|
|
7570
|
+
}).join("\n\n");
|
|
7571
|
+
}
|
|
7572
|
+
function formatExecutedTools(executedTools) {
|
|
7573
|
+
if (!executedTools || executedTools.length === 0) {
|
|
7574
|
+
return "No external tools were executed for data fetching.";
|
|
7575
|
+
}
|
|
7576
|
+
return "The following external tools were executed to fetch data.\n" + executedTools.map((tool, idx) => {
|
|
7577
|
+
let outputSchemaText = "Not available";
|
|
7578
|
+
let fieldNamesList = "";
|
|
7579
|
+
const recordCount = tool.result?._totalRecords ?? "unknown";
|
|
7580
|
+
let metadataText = "";
|
|
7581
|
+
if (tool.result?._metadata && Object.keys(tool.result._metadata).length > 0) {
|
|
7582
|
+
const metadataEntries = Object.entries(tool.result._metadata).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
7583
|
+
metadataText = `
|
|
7584
|
+
\u{1F4CB} METADATA: ${metadataEntries}`;
|
|
7585
|
+
}
|
|
7586
|
+
if (tool.outputSchema) {
|
|
7587
|
+
const fields = tool.outputSchema.fields || [];
|
|
7588
|
+
const numericFields = fields.filter((f) => f.type === "number").map((f) => f.name);
|
|
7589
|
+
const stringFields = fields.filter((f) => f.type === "string").map((f) => f.name);
|
|
7590
|
+
fieldNamesList = `
|
|
7591
|
+
\u{1F4CA} NUMERIC FIELDS (use for yAxisKey, valueKey, aggregationField): ${numericFields.join(", ") || "none"}
|
|
7592
|
+
\u{1F4DD} STRING FIELDS (use for xAxisKey, groupBy, nameKey): ${stringFields.join(", ") || "none"}`;
|
|
7593
|
+
const fieldsText = fields.map(
|
|
7594
|
+
(f) => ` "${f.name}" (${f.type}): ${f.description}`
|
|
7595
|
+
).join("\n");
|
|
7596
|
+
outputSchemaText = `${tool.outputSchema.description}
|
|
7597
|
+
Fields:
|
|
7598
|
+
${fieldsText}`;
|
|
7599
|
+
}
|
|
7600
|
+
return `${idx + 1}. **${tool.name}**
|
|
7601
|
+
toolId: "${tool.id}"
|
|
7602
|
+
toolName: "${tool.name}"
|
|
7603
|
+
parameters: ${JSON.stringify(tool.params || {})}
|
|
7604
|
+
recordCount: ${recordCount} rows returned${metadataText}
|
|
7605
|
+
outputSchema: ${outputSchemaText}${fieldNamesList}`;
|
|
7606
|
+
}).join("\n\n");
|
|
7607
|
+
}
|
|
7608
|
+
function tryStreamAnswerComponent(text, components, executedTools, collections, apiKey, callback) {
|
|
7609
|
+
const hasMatch = text.match(/"hasAnswerComponent"\s*:\s*(true|false)/);
|
|
7610
|
+
if (!hasMatch || hasMatch[1] !== "true") return false;
|
|
7611
|
+
const startMatch = text.match(/"answerComponent"\s*:\s*\{/);
|
|
7612
|
+
if (!startMatch) return false;
|
|
7613
|
+
const startPos = startMatch.index + startMatch[0].length - 1;
|
|
7614
|
+
let depth = 0;
|
|
7615
|
+
let inString = false;
|
|
7616
|
+
let escapeNext = false;
|
|
7617
|
+
let endPos = -1;
|
|
7618
|
+
for (let i = startPos; i < text.length; i++) {
|
|
7619
|
+
const char = text[i];
|
|
7620
|
+
if (escapeNext) {
|
|
7621
|
+
escapeNext = false;
|
|
7622
|
+
continue;
|
|
7623
|
+
}
|
|
7624
|
+
if (char === "\\") {
|
|
7625
|
+
escapeNext = true;
|
|
7626
|
+
continue;
|
|
7627
|
+
}
|
|
7628
|
+
if (char === '"') {
|
|
7629
|
+
inString = !inString;
|
|
7630
|
+
continue;
|
|
7631
|
+
}
|
|
7632
|
+
if (!inString) {
|
|
7633
|
+
if (char === "{") depth++;
|
|
7634
|
+
else if (char === "}") {
|
|
7635
|
+
depth--;
|
|
7636
|
+
if (depth === 0) {
|
|
7637
|
+
endPos = i + 1;
|
|
7638
|
+
break;
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7357
7642
|
}
|
|
7358
|
-
if (
|
|
7359
|
-
|
|
7643
|
+
if (endPos <= startPos) return false;
|
|
7644
|
+
try {
|
|
7645
|
+
const answerData = JSON.parse(text.substring(startPos, endPos));
|
|
7646
|
+
if (!answerData?.componentId) return false;
|
|
7647
|
+
const original = components.find((c) => c.id === answerData.componentId);
|
|
7648
|
+
if (!original) return false;
|
|
7649
|
+
const answerComponent = {
|
|
7650
|
+
...original,
|
|
7651
|
+
props: { ...original.props, ...answerData.props }
|
|
7652
|
+
};
|
|
7653
|
+
const answerProps = answerComponent.props;
|
|
7654
|
+
const sql = answerProps?.externalTool?.parameters?.sql;
|
|
7655
|
+
if (sql && collections?.["external-tools"]?.["execute"]) {
|
|
7656
|
+
const toolId = answerProps.externalTool.toolId;
|
|
7657
|
+
const toolName = answerProps.externalTool.toolName;
|
|
7658
|
+
(async () => {
|
|
7659
|
+
try {
|
|
7660
|
+
const result = await collections["external-tools"]["execute"]({
|
|
7661
|
+
toolId,
|
|
7662
|
+
toolName,
|
|
7663
|
+
sql,
|
|
7664
|
+
data: {}
|
|
7665
|
+
});
|
|
7666
|
+
if (result?.success === false || result?.error) {
|
|
7667
|
+
logger.warn(`[AgentComponentGen] Answer component query failed: ${result?.error}`);
|
|
7668
|
+
return;
|
|
7669
|
+
}
|
|
7670
|
+
queryCache.set(sql, result?.data ?? result);
|
|
7671
|
+
callback(answerComponent);
|
|
7672
|
+
} catch (err) {
|
|
7673
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7674
|
+
logger.warn(`[AgentComponentGen] Answer component validation failed: ${msg}`);
|
|
7675
|
+
}
|
|
7676
|
+
})();
|
|
7677
|
+
} else {
|
|
7678
|
+
callback(answerComponent);
|
|
7679
|
+
}
|
|
7680
|
+
return true;
|
|
7681
|
+
} catch {
|
|
7682
|
+
return false;
|
|
7360
7683
|
}
|
|
7361
|
-
return String(content);
|
|
7362
7684
|
}
|
|
7363
|
-
function
|
|
7364
|
-
if (
|
|
7365
|
-
|
|
7685
|
+
async function validateExternalToolQueries(components, collections, executedTools, apiKey) {
|
|
7686
|
+
if (!collections?.["external-tools"]?.["execute"]) {
|
|
7687
|
+
logger.warn(`[AgentComponentGen] external-tools.execute not available, skipping validation`);
|
|
7688
|
+
return components;
|
|
7366
7689
|
}
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7690
|
+
const validated = [];
|
|
7691
|
+
const withSql = [];
|
|
7692
|
+
const withoutSql = [];
|
|
7693
|
+
for (const comp of components) {
|
|
7694
|
+
const sql = comp.props?.externalTool?.parameters?.sql;
|
|
7695
|
+
if (sql) {
|
|
7696
|
+
withSql.push(comp);
|
|
7697
|
+
} else {
|
|
7698
|
+
withoutSql.push(comp);
|
|
7371
7699
|
}
|
|
7372
|
-
|
|
7373
|
-
|
|
7700
|
+
}
|
|
7701
|
+
validated.push(...withoutSql);
|
|
7702
|
+
if (withSql.length === 0) return validated;
|
|
7703
|
+
const sqlGroups = /* @__PURE__ */ new Map();
|
|
7704
|
+
for (const comp of withSql) {
|
|
7705
|
+
const sql = comp.props.externalTool.parameters.sql;
|
|
7706
|
+
const normalized = sql.replace(/\s+/g, " ").trim();
|
|
7707
|
+
if (!sqlGroups.has(normalized)) {
|
|
7708
|
+
sqlGroups.set(normalized, []);
|
|
7374
7709
|
}
|
|
7375
|
-
|
|
7710
|
+
sqlGroups.get(normalized).push(comp);
|
|
7376
7711
|
}
|
|
7377
|
-
|
|
7378
|
-
}
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7712
|
+
const uniqueQueries = Array.from(sqlGroups.entries());
|
|
7713
|
+
logger.info(`[AgentComponentGen] Validating ${uniqueQueries.length} unique queries (${withSql.length} components)...`);
|
|
7714
|
+
const results = await Promise.allSettled(
|
|
7715
|
+
uniqueQueries.map(([_, comps]) => validateSingleExternalToolQuery(comps[0], collections, executedTools, apiKey))
|
|
7716
|
+
);
|
|
7717
|
+
for (let i = 0; i < results.length; i++) {
|
|
7718
|
+
const result = results[i];
|
|
7719
|
+
const [_, groupComps] = uniqueQueries[i];
|
|
7720
|
+
if (result.status === "fulfilled" && result.value) {
|
|
7721
|
+
validated.push(result.value);
|
|
7722
|
+
for (let j = 1; j < groupComps.length; j++) {
|
|
7723
|
+
const fixedSql = result.value.props?.externalTool?.parameters?.sql;
|
|
7724
|
+
if (fixedSql) {
|
|
7725
|
+
const siblingProps = groupComps[j].props;
|
|
7726
|
+
validated.push({
|
|
7727
|
+
...groupComps[j],
|
|
7728
|
+
props: {
|
|
7729
|
+
...groupComps[j].props,
|
|
7730
|
+
externalTool: {
|
|
7731
|
+
...siblingProps.externalTool,
|
|
7732
|
+
parameters: { ...siblingProps.externalTool.parameters, sql: fixedSql }
|
|
7733
|
+
}
|
|
7734
|
+
}
|
|
7735
|
+
});
|
|
7736
|
+
} else {
|
|
7737
|
+
validated.push(groupComps[j]);
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
} else {
|
|
7741
|
+
const reason = result.status === "rejected" ? result.reason : "validation failed";
|
|
7742
|
+
for (const comp of groupComps) {
|
|
7743
|
+
logger.warn(`[AgentComponentGen] Excluded ${comp.name}: ${reason}`);
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7382
7746
|
}
|
|
7383
|
-
|
|
7384
|
-
|
|
7747
|
+
logger.info(`[AgentComponentGen] Validation complete: ${validated.length}/${components.length} components passed`);
|
|
7748
|
+
return validated;
|
|
7749
|
+
}
|
|
7750
|
+
async function validateSingleExternalToolQuery(component, collections, executedTools, apiKey) {
|
|
7751
|
+
const compProps = component.props;
|
|
7752
|
+
const toolId = compProps?.externalTool?.toolId;
|
|
7753
|
+
const toolName = compProps?.externalTool?.toolName;
|
|
7754
|
+
let currentSql = compProps?.externalTool?.parameters?.sql;
|
|
7755
|
+
if (!toolId || !currentSql) return component;
|
|
7756
|
+
currentSql = ensureQueryLimit(currentSql, 10, MAX_COMPONENT_QUERY_LIMIT);
|
|
7757
|
+
let attempts = 0;
|
|
7758
|
+
while (attempts < MAX_QUERY_VALIDATION_RETRIES) {
|
|
7759
|
+
attempts++;
|
|
7760
|
+
try {
|
|
7761
|
+
logger.info(`[AgentComponentGen] Validating ${component.name} (attempt ${attempts}/${MAX_QUERY_VALIDATION_RETRIES})`);
|
|
7762
|
+
const result = await collections["external-tools"]["execute"]({
|
|
7763
|
+
toolId,
|
|
7764
|
+
toolName,
|
|
7765
|
+
sql: currentSql,
|
|
7766
|
+
data: {}
|
|
7767
|
+
});
|
|
7768
|
+
if (result?.success === false || result?.error) {
|
|
7769
|
+
const errorMsg = result?.error || "Unknown error";
|
|
7770
|
+
throw new Error(typeof errorMsg === "string" ? errorMsg : JSON.stringify(errorMsg));
|
|
7771
|
+
}
|
|
7772
|
+
const rawToolData = result?.data ?? result;
|
|
7773
|
+
queryCache.set(currentSql, rawToolData);
|
|
7774
|
+
logger.info(`[AgentComponentGen] \u2713 ${component.name} validated (attempt ${attempts})`);
|
|
7775
|
+
return {
|
|
7776
|
+
...component,
|
|
7777
|
+
props: {
|
|
7778
|
+
...component.props,
|
|
7779
|
+
externalTool: {
|
|
7780
|
+
...compProps.externalTool,
|
|
7781
|
+
parameters: {
|
|
7782
|
+
...compProps.externalTool.parameters,
|
|
7783
|
+
sql: currentSql
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7787
|
+
};
|
|
7788
|
+
} catch (error) {
|
|
7789
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
7790
|
+
logger.warn(`[AgentComponentGen] \u2717 ${component.name} failed (attempt ${attempts}): ${errorMsg}`);
|
|
7791
|
+
if (attempts >= MAX_QUERY_VALIDATION_RETRIES) {
|
|
7792
|
+
logger.error(`[AgentComponentGen] Max retries reached for ${component.name}, excluding`);
|
|
7793
|
+
return null;
|
|
7794
|
+
}
|
|
7795
|
+
try {
|
|
7796
|
+
const fixedSql = await requestExternalToolQueryFix(
|
|
7797
|
+
currentSql,
|
|
7798
|
+
errorMsg,
|
|
7799
|
+
component,
|
|
7800
|
+
executedTools,
|
|
7801
|
+
toolId,
|
|
7802
|
+
apiKey
|
|
7803
|
+
);
|
|
7804
|
+
if (fixedSql && fixedSql !== currentSql) {
|
|
7805
|
+
currentSql = ensureQueryLimit(fixedSql, 10, MAX_COMPONENT_QUERY_LIMIT);
|
|
7806
|
+
logger.info(`[AgentComponentGen] LLM provided fix for ${component.name}, retrying...`);
|
|
7807
|
+
} else {
|
|
7808
|
+
logger.warn(`[AgentComponentGen] LLM returned same or empty query, stopping retries`);
|
|
7809
|
+
return null;
|
|
7810
|
+
}
|
|
7811
|
+
} catch (fixError) {
|
|
7812
|
+
const fixMsg = fixError instanceof Error ? fixError.message : String(fixError);
|
|
7813
|
+
logger.error(`[AgentComponentGen] Failed to get LLM fix: ${fixMsg}`);
|
|
7814
|
+
return null;
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7385
7817
|
}
|
|
7386
|
-
return
|
|
7818
|
+
return null;
|
|
7387
7819
|
}
|
|
7820
|
+
async function requestExternalToolQueryFix(failedSql, errorMessage, component, executedTools, toolId, apiKey) {
|
|
7821
|
+
const executedTool = executedTools.find((t) => t.id === toolId);
|
|
7822
|
+
const sourceSchema = executedTool?.sourceSchema || "Schema not available";
|
|
7823
|
+
const sourceType = executedTool?.sourceType || "postgresql";
|
|
7824
|
+
const databaseRules = await promptLoader.loadDatabaseRulesForType(sourceType);
|
|
7825
|
+
const prompt = `You are a SQL expert. Fix the following SQL query that failed execution.
|
|
7388
7826
|
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7827
|
+
## Database Schema
|
|
7828
|
+
${sourceSchema}
|
|
7829
|
+
|
|
7830
|
+
## Database-Specific SQL Rules
|
|
7831
|
+
${databaseRules}
|
|
7832
|
+
|
|
7833
|
+
## Component Context
|
|
7834
|
+
- Component Name: ${component.name}
|
|
7835
|
+
- Component Type: ${component.type}
|
|
7836
|
+
- Title: ${component.props?.title || "N/A"}
|
|
7837
|
+
|
|
7838
|
+
## Failed Query
|
|
7839
|
+
\`\`\`sql
|
|
7840
|
+
${failedSql}
|
|
7841
|
+
\`\`\`
|
|
7842
|
+
|
|
7843
|
+
## Error Message
|
|
7844
|
+
${errorMessage}
|
|
7845
|
+
|
|
7846
|
+
## Instructions
|
|
7847
|
+
1. Analyze the error message and identify what caused the query to fail
|
|
7848
|
+
2. Fix the query to resolve the error while preserving the original intent
|
|
7849
|
+
3. Ensure the fixed query follows the database-specific SQL rules above
|
|
7850
|
+
4. Return ONLY the fixed SQL query, no explanations or markdown
|
|
7851
|
+
|
|
7852
|
+
Fixed SQL query:`;
|
|
7853
|
+
const response = await LLM.text(
|
|
7854
|
+
{
|
|
7855
|
+
sys: "You are a SQL expert. Return only the fixed SQL query with no additional text, explanations, or markdown formatting.",
|
|
7856
|
+
user: prompt
|
|
7857
|
+
},
|
|
7858
|
+
{
|
|
7859
|
+
model: "anthropic/claude-haiku-4-5-20251001",
|
|
7860
|
+
maxTokens: 2048,
|
|
7861
|
+
temperature: 0,
|
|
7862
|
+
apiKey
|
|
7863
|
+
}
|
|
7864
|
+
);
|
|
7865
|
+
let fixedQuery = response.trim();
|
|
7866
|
+
fixedQuery = fixedQuery.replace(/^```sql\s*/i, "").replace(/\s*```$/i, "");
|
|
7867
|
+
fixedQuery = fixedQuery.replace(/^```\s*/i, "").replace(/\s*```$/i, "");
|
|
7868
|
+
const { query: validatedQuery } = validateAndFixSqlQuery(fixedQuery);
|
|
7869
|
+
return validatedQuery;
|
|
7403
7870
|
}
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7871
|
+
|
|
7872
|
+
// src/userResponse/anthropic.ts
|
|
7873
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
7874
|
+
|
|
7875
|
+
// src/userResponse/schema.ts
|
|
7876
|
+
var import_path4 = __toESM(require("path"));
|
|
7877
|
+
var import_fs5 = __toESM(require("fs"));
|
|
7878
|
+
var Schema = class {
|
|
7879
|
+
constructor(schemaFilePath) {
|
|
7880
|
+
this.cachedSchema = null;
|
|
7881
|
+
this.schemaFilePath = schemaFilePath || import_path4.default.join(process.cwd(), "../analysis/data/schema.json");
|
|
7414
7882
|
}
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7883
|
+
/**
|
|
7884
|
+
* Gets the database schema from the schema file
|
|
7885
|
+
* @returns Parsed schema object or null if error occurs
|
|
7886
|
+
*/
|
|
7887
|
+
getDatabaseSchema() {
|
|
7888
|
+
try {
|
|
7889
|
+
const dir = import_path4.default.dirname(this.schemaFilePath);
|
|
7890
|
+
if (!import_fs5.default.existsSync(dir)) {
|
|
7891
|
+
logger.info(`Creating directory structure: ${dir}`);
|
|
7892
|
+
import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
7893
|
+
}
|
|
7894
|
+
if (!import_fs5.default.existsSync(this.schemaFilePath)) {
|
|
7895
|
+
logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
|
|
7896
|
+
const initialSchema = {
|
|
7897
|
+
database: "",
|
|
7898
|
+
schema: "",
|
|
7899
|
+
description: "",
|
|
7900
|
+
tables: [],
|
|
7901
|
+
relationships: []
|
|
7902
|
+
};
|
|
7903
|
+
import_fs5.default.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
|
|
7904
|
+
this.cachedSchema = initialSchema;
|
|
7905
|
+
return initialSchema;
|
|
7906
|
+
}
|
|
7907
|
+
const fileContent = import_fs5.default.readFileSync(this.schemaFilePath, "utf-8");
|
|
7908
|
+
const schema2 = JSON.parse(fileContent);
|
|
7909
|
+
this.cachedSchema = schema2;
|
|
7910
|
+
return schema2;
|
|
7911
|
+
} catch (error) {
|
|
7912
|
+
logger.error("Error parsing schema file:", error);
|
|
7913
|
+
return null;
|
|
7423
7914
|
}
|
|
7424
7915
|
}
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7916
|
+
/**
|
|
7917
|
+
* Gets the cached schema or loads it if not cached
|
|
7918
|
+
* @returns Cached schema or freshly loaded schema
|
|
7919
|
+
*/
|
|
7920
|
+
getSchema() {
|
|
7921
|
+
if (this.cachedSchema) {
|
|
7922
|
+
return this.cachedSchema;
|
|
7923
|
+
}
|
|
7924
|
+
return this.getDatabaseSchema();
|
|
7433
7925
|
}
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
const
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7926
|
+
/**
|
|
7927
|
+
* Generates database schema documentation for LLM from Snowflake JSON schema
|
|
7928
|
+
* @returns Formatted schema documentation string
|
|
7929
|
+
*/
|
|
7930
|
+
generateSchemaDocumentation() {
|
|
7931
|
+
const schema2 = this.getSchema();
|
|
7932
|
+
if (!schema2) {
|
|
7933
|
+
logger.warn("No database schema found.");
|
|
7934
|
+
return "No database schema available.";
|
|
7935
|
+
}
|
|
7936
|
+
const tables = [];
|
|
7937
|
+
tables.push(`Database: ${schema2.database}`);
|
|
7938
|
+
tables.push(`Schema: ${schema2.schema}`);
|
|
7939
|
+
tables.push(`Description: ${schema2.description}`);
|
|
7940
|
+
tables.push("");
|
|
7941
|
+
tables.push("=".repeat(80));
|
|
7942
|
+
tables.push("");
|
|
7943
|
+
for (const table of schema2.tables) {
|
|
7944
|
+
const tableInfo = [];
|
|
7945
|
+
tableInfo.push(`TABLE: ${table.fullName}`);
|
|
7946
|
+
tableInfo.push(`Description: ${table.description}`);
|
|
7947
|
+
tableInfo.push(`Row Count: ~${table.rowCount.toLocaleString()}`);
|
|
7948
|
+
tableInfo.push("");
|
|
7949
|
+
tableInfo.push("Columns:");
|
|
7950
|
+
for (const column of table.columns) {
|
|
7951
|
+
let columnLine = ` - ${column.name}: ${column.type}`;
|
|
7952
|
+
if (column.isPrimaryKey) {
|
|
7953
|
+
columnLine += " (PRIMARY KEY)";
|
|
7954
|
+
}
|
|
7955
|
+
if (column.isForeignKey && column.references) {
|
|
7956
|
+
columnLine += ` (FK -> ${column.references.table}.${column.references.column})`;
|
|
7957
|
+
}
|
|
7958
|
+
if (!column.nullable) {
|
|
7959
|
+
columnLine += " NOT NULL";
|
|
7960
|
+
}
|
|
7961
|
+
if (column.description) {
|
|
7962
|
+
columnLine += ` - ${column.description}`;
|
|
7963
|
+
}
|
|
7964
|
+
tableInfo.push(columnLine);
|
|
7965
|
+
if (column.sampleValues && column.sampleValues.length > 0) {
|
|
7966
|
+
tableInfo.push(` Sample values: [${column.sampleValues.join(", ")}]`);
|
|
7967
|
+
}
|
|
7968
|
+
if (column.statistics) {
|
|
7969
|
+
const stats = column.statistics;
|
|
7970
|
+
if (stats.min !== void 0 && stats.max !== void 0) {
|
|
7971
|
+
tableInfo.push(` Range: ${stats.min} to ${stats.max}`);
|
|
7972
|
+
}
|
|
7973
|
+
if (stats.distinct !== void 0) {
|
|
7974
|
+
tableInfo.push(` Distinct values: ${stats.distinct.toLocaleString()}`);
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
}
|
|
7978
|
+
tableInfo.push("");
|
|
7979
|
+
tables.push(tableInfo.join("\n"));
|
|
7446
7980
|
}
|
|
7981
|
+
tables.push("=".repeat(80));
|
|
7982
|
+
tables.push("");
|
|
7983
|
+
tables.push("TABLE RELATIONSHIPS:");
|
|
7984
|
+
tables.push("");
|
|
7985
|
+
for (const rel of schema2.relationships) {
|
|
7986
|
+
tables.push(`${rel.from} -> ${rel.to} (${rel.type}): ${rel.keys.join(" = ")}`);
|
|
7987
|
+
}
|
|
7988
|
+
return tables.join("\n");
|
|
7447
7989
|
}
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7990
|
+
/**
|
|
7991
|
+
* Clears the cached schema, forcing a reload on next access
|
|
7992
|
+
*/
|
|
7993
|
+
clearCache() {
|
|
7994
|
+
this.cachedSchema = null;
|
|
7451
7995
|
}
|
|
7452
|
-
|
|
7453
|
-
|
|
7996
|
+
/**
|
|
7997
|
+
* Sets a custom schema file path
|
|
7998
|
+
* @param filePath - Path to the schema file
|
|
7999
|
+
*/
|
|
8000
|
+
setSchemaPath(filePath) {
|
|
8001
|
+
this.schemaFilePath = filePath;
|
|
8002
|
+
this.clearCache();
|
|
7454
8003
|
}
|
|
7455
|
-
|
|
7456
|
-
|
|
8004
|
+
};
|
|
8005
|
+
var schema = new Schema();
|
|
7457
8006
|
|
|
7458
8007
|
// src/userResponse/services/query-execution-service.ts
|
|
7459
8008
|
var QueryExecutionService = class {
|
|
@@ -9386,7 +9935,7 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9386
9935
|
let layoutDescription = "Multi-component dashboard";
|
|
9387
9936
|
let actions = [];
|
|
9388
9937
|
if (!hasExecutedTools) {
|
|
9389
|
-
logger.info(`[AgentFlow] No tools executed \u2014 general question,
|
|
9938
|
+
logger.info(`[AgentFlow] No tools executed \u2014 general question, wrapping in DynamicMarkdownBlock`);
|
|
9390
9939
|
const nextQuestions = await llmInstance.generateNextQuestions(
|
|
9391
9940
|
prompt,
|
|
9392
9941
|
null,
|
|
@@ -9396,6 +9945,18 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9396
9945
|
textResponse
|
|
9397
9946
|
);
|
|
9398
9947
|
actions = convertQuestionsToActions(nextQuestions);
|
|
9948
|
+
const markdownContent = textResponse.replace(/<DashboardComponents>[\s\S]*?<\/DashboardComponents>/g, "").trim();
|
|
9949
|
+
matchedComponents = [{
|
|
9950
|
+
id: "dynamic-markdown-block",
|
|
9951
|
+
name: "DynamicMarkdownBlock",
|
|
9952
|
+
type: "MarkdownBlock",
|
|
9953
|
+
description: "Text response rendered as markdown",
|
|
9954
|
+
props: {
|
|
9955
|
+
content: markdownContent
|
|
9956
|
+
}
|
|
9957
|
+
}];
|
|
9958
|
+
layoutTitle = "";
|
|
9959
|
+
layoutDescription = "";
|
|
9399
9960
|
} else if (components && components.length > 0) {
|
|
9400
9961
|
logger.info(`[AgentFlow] ${agentResponse.executedTools.length} tools executed \u2014 generating components`);
|
|
9401
9962
|
if (streamBuffer.hasCallback()) {
|
|
@@ -9410,18 +9971,16 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9410
9971
|
/<DataTable>[\s\S]*?<\/DataTable>/g,
|
|
9411
9972
|
"<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>"
|
|
9412
9973
|
);
|
|
9413
|
-
const matchResult = await
|
|
9414
|
-
sanitizedTextResponse,
|
|
9974
|
+
const matchResult = await generateAgentComponents({
|
|
9975
|
+
analysisContent: sanitizedTextResponse,
|
|
9415
9976
|
components,
|
|
9416
|
-
prompt,
|
|
9977
|
+
userPrompt: prompt,
|
|
9978
|
+
executedTools: agentResponse.executedTools,
|
|
9979
|
+
collections,
|
|
9417
9980
|
apiKey,
|
|
9418
9981
|
componentStreamCallback,
|
|
9419
|
-
[],
|
|
9420
|
-
// deferredTools — MainAgent handles tool execution
|
|
9421
|
-
agentResponse.executedTools,
|
|
9422
|
-
collections,
|
|
9423
9982
|
userId
|
|
9424
|
-
);
|
|
9983
|
+
});
|
|
9425
9984
|
matchedComponents = matchResult.components;
|
|
9426
9985
|
layoutTitle = matchResult.layoutTitle;
|
|
9427
9986
|
layoutDescription = matchResult.layoutDescription;
|
|
@@ -9429,9 +9988,11 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
9429
9988
|
}
|
|
9430
9989
|
const securedComponents = matchedComponents.map((comp) => {
|
|
9431
9990
|
const props = { ...comp.props };
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
const
|
|
9991
|
+
const sqlValue = props.externalTool?.parameters?.sql || props.externalTool?.parameters?.query;
|
|
9992
|
+
if (sqlValue) {
|
|
9993
|
+
const { sql, query, ...restParams } = props.externalTool.parameters;
|
|
9994
|
+
const cachedData = queryCache.get(sqlValue);
|
|
9995
|
+
const queryId = queryCache.storeQuery(sqlValue, cachedData);
|
|
9435
9996
|
props.externalTool = {
|
|
9436
9997
|
...props.externalTool,
|
|
9437
9998
|
parameters: { queryId, ...restParams }
|
|
@@ -9550,18 +10111,10 @@ async function saveConversation(params) {
|
|
|
9550
10111
|
}
|
|
9551
10112
|
try {
|
|
9552
10113
|
logger.info(`[CONVERSATION_SAVER] Saving conversation for userId: ${userId}, uiBlockId: ${uiBlockId}, threadId: ${threadId}`);
|
|
9553
|
-
const userIdNumber = Number(userId);
|
|
9554
|
-
if (isNaN(userIdNumber)) {
|
|
9555
|
-
logger.warn(`[CONVERSATION_SAVER] Invalid userId: ${userId} (not a valid number)`);
|
|
9556
|
-
return {
|
|
9557
|
-
success: false,
|
|
9558
|
-
error: `Invalid userId: ${userId} (not a valid number)`
|
|
9559
|
-
};
|
|
9560
|
-
}
|
|
9561
10114
|
const dbUIBlock = transformUIBlockForDB(uiblock, userPrompt, uiBlockId);
|
|
9562
10115
|
logger.debug(`[CONVERSATION_SAVER] Transformed UIBlock for DB: ${JSON.stringify(dbUIBlock)}`);
|
|
9563
10116
|
const saveResult = await collections["user-conversations"]["create"]({
|
|
9564
|
-
userId
|
|
10117
|
+
userId,
|
|
9565
10118
|
userPrompt,
|
|
9566
10119
|
uiblock: dbUIBlock,
|
|
9567
10120
|
threadId
|
|
@@ -9582,7 +10135,7 @@ async function saveConversation(params) {
|
|
|
9582
10135
|
userPrompt,
|
|
9583
10136
|
uiBlock: dbUIBlock,
|
|
9584
10137
|
// Use the transformed UIBlock
|
|
9585
|
-
userId
|
|
10138
|
+
userId
|
|
9586
10139
|
});
|
|
9587
10140
|
if (embedResult?.success) {
|
|
9588
10141
|
logger.info("[CONVERSATION_SAVER] Successfully created embedding");
|