Refactor AI panel generation to remove table_name parameter and enhance prompt clarity#1637
Conversation
…ce prompt clarity
There was a problem hiding this comment.
Pull request overview
Refactors AI-driven dashboard widget generation to remove the tableName query parameter and instead have the AI discover relevant tables via tool calls (getTablesList, getTableStructure), with updated prompts and E2E tests.
Changes:
- Removed
tableNamefrom the generate-widget-with-AI controller API and data structure. - Updated the AI panel generation use case to run a streaming tool-call loop for schema discovery and improved prompt instructions.
- Updated SaaS E2E tests to use the new endpoint contract and mock tool-call streaming.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| backend/test/ava-tests/saas-tests/dashboard-ai-widget-e2e.test.ts | Updates E2E tests to call the endpoint without tableName and mocks tool-call streaming behavior. |
| backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts | Replaces table-bound prompting with a tool-loop approach; adds JSON extraction and updates EXPLAIN-based refinement flow. |
| backend/src/entities/visualizations/panel-position/panel-position.controller.ts | Removes tableName query parameter from the API and updates Swagger docs/timeout. |
| backend/src/entities/visualizations/panel-position/data-structures/generate-panel-position-with-ai.ds.ts | Removes table_name from the use case input structure. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let userEmail: string; | ||
| if (isConnectionTypeAgent(foundConnection.type)) { | ||
| userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); |
There was a problem hiding this comment.
userEmail is declared but not initialized for non-agent connections, and getUserEmailOrReturnNull() can return null. This value is then passed into the tool loop where agent DAOs require a real email for getTablesFromDB/getTableStructure. Initialize userEmail to null/undefined explicitly, widen the parameter types accordingly, and for agent connections fail fast with a clear error if the email cannot be resolved.
| let userEmail: string; | |
| if (isConnectionTypeAgent(foundConnection.type)) { | |
| userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); | |
| let userEmail: string = ''; | |
| if (isConnectionTypeAgent(foundConnection.type)) { | |
| const resolvedUserEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); | |
| if (!resolvedUserEmail) { | |
| throw new BadRequestException( | |
| 'Unable to resolve user email for agent connection. A valid email is required to use AI tools.', | |
| ); | |
| } | |
| userEmail = resolvedUserEmail; |
| try { | ||
| const explainQuery = `EXPLAIN ${query.replace(/;\s*$/, '')}`; | ||
| const result = await (dao as IDataAccessObject).executeRawQuery(explainQuery, tableName); | ||
| const result = await (dao as IDataAccessObject).executeRawQuery(explainQuery, ''); | ||
| return { success: true, result: JSON.stringify(result, null, 2) }; |
There was a problem hiding this comment.
runExplainQuery calls executeRawQuery(explainQuery, '') without passing userEmail. For agent-backed connections, executeRawQuery requires both tableName and userEmail, so this will send an empty table name and an undefined email to the agent and likely fail. Pass through userEmail (and a meaningful table name if required by the agent) when calling executeRawQuery, similar to other places that execute queries against agent connections.
|
|
||
| const firstBrace = response.indexOf('{'); | ||
| if (firstBrace !== -1) { | ||
| return response.slice(firstBrace); |
There was a problem hiding this comment.
extractJsonFromResponse slices from the first { to the end of the response, but does not trim anything after the closing }. If the model returns valid JSON followed by any trailing text, JSON.parse will still fail even after cleanAIJsonResponse. Consider extracting up to the last } (or reusing the existing JSON sanitization helper) so the parser is resilient to common LLM formatting drift.
| return response.slice(firstBrace); | |
| const lastBrace = response.lastIndexOf('}'); | |
| if (lastBrace !== -1 && lastBrace > firstBrace) { | |
| return response.slice(firstBrace, lastBrace + 1).trim(); | |
| } | |
| return response.slice(firstBrace).trim(); |
No description provided.