internal/functions/generation/Get-SldgAISchemaAnalysis.ps1
|
function Get-SldgAISchemaAnalysis { <# .SYNOPSIS Uses a smart AI model to deeply analyze the schema and sample data, producing per-table generation notes for the batch-generation model. .DESCRIPTION Sends the full schema context together with sample data rows (queried from the database) to the 'schema-analysis' AI purpose. The response contains detailed, actionable per-table generation instructions that are later injected into the batch-generation prompt so that even a small local model can produce realistic, relationship-aware data. This is the "smart tier" in the two-tier AI architecture: 1. schema-analysis (smart cloud model) → produces notes 2. batch-generation (local model) → uses notes to generate rows #> [CmdletBinding()] param ( [Parameter(Mandatory)] [SqlLabDataGenerator.SchemaModel]$SchemaModel, [int]$BaseRowCount = 100, [string]$Locale, ## Active connection info — used to query sample data [SqlLabDataGenerator.Connection]$ConnectionInfo, ## Database provider object (has FunctionMap.ReadData) [SqlLabDataGenerator.SqlProvider]$Provider, [int]$SampleRows = 5 ) $aiProvider = Get-PSFConfigValue -FullName 'SqlLabDataGenerator.AI.Provider' if ($aiProvider -eq 'None') { Write-PSFMessage -Level Verbose -Message $script:strings.'AI.SchemaAnalysisSkipped' return $null } if (-not $Locale) { $Locale = Get-PSFConfigValue -FullName 'SqlLabDataGenerator.Generation.Locale' } # ── Build schema description with sample data ── $schemaSummary = foreach ($table in $SchemaModel.Tables) { $colLines = foreach ($col in $table.Columns) { $fk = if ($col.ForeignKey) { " -> $($col.ForeignKey.ReferencedTable).$($col.ForeignKey.ReferencedColumn)" } else { "" } $flags = @() if ($col.IsPrimaryKey) { $flags += 'PK' } if ($col.IsIdentity) { $flags += 'IDENTITY' } if ($col.IsNullable) { $flags += 'NULL' } if ($col.IsUnique) { $flags += 'UNIQUE' } $flagStr = if ($flags) { " [$($flags -join ',')]" } else { "" } $semanticStr = if ($col.SemanticType) { " (semantic: $($col.SemanticType))" } else { "" } $checkStr = if ($col.CheckConstraints -and $col.CheckConstraints.Count -gt 0) { " CHECK($($col.CheckConstraints -join '; '))" } else { "" } $typeSize = if ($col.NumericPrecision -and $col.DataType -match '^(decimal|numeric|money|smallmoney)$') { $s = if ($col.NumericScale) { $col.NumericScale } else { 0 } "($($col.NumericPrecision),$s)" } elseif ($col.MaxLength -and $col.MaxLength -gt 0) { "($($col.MaxLength))" } else { "" } " - $($col.ColumnName) $($col.DataType)$typeSize$flagStr$fk$semanticStr$checkStr" } $tableText = "TABLE: $($table.FullName) ($($table.ColumnCount) columns)`n$($colLines -join "`n")" # Query sample data if connection is available if ($ConnectionInfo -and $Provider -and $Provider.FunctionMap.ReadData) { try { $sampleData = & $Provider.FunctionMap.ReadData -ConnectionInfo $ConnectionInfo ` -SchemaName $table.SchemaName -TableName $table.TableName -TopN $SampleRows if ($sampleData -and $sampleData.Rows.Count -gt 0) { $sampleLines = foreach ($row in $sampleData.Rows) { $vals = foreach ($dtCol in $sampleData.Columns) { $v = $row[$dtCol.ColumnName] if ($v -is [DBNull]) { "$($dtCol.ColumnName)=NULL" } else { # Sanitize sample values to prevent prompt injection from DB content $sanitized = "$v" -replace '[\r\n]+', ' ' $sanitized = Remove-SldgUnsafeChar -Text $sanitized -Mode General -MaxLength 100 "$($dtCol.ColumnName)=$sanitized" } } " { $($vals -join ', ') }" } $tableText += "`n Sample data ($($sampleData.Rows.Count) rows):`n$($sampleLines -join "`n")" } else { $tableText += "`n Sample data: (empty table)" } } catch { Write-PSFMessage -Level Verbose -Message ($script:strings.'AI.SchemaAnalysisSampleFailed' -f $table.FullName, $_) $tableText += "`n Sample data: (query failed)" } } $tableText } $schemaText = $schemaSummary -join "`n`n" $relationshipGraph = Build-SldgRelationshipGraph -SchemaModel $SchemaModel $systemPrompt = Resolve-SldgPromptTemplate -Purpose 'schema-analysis' -Variables @{ BaseRowCount = $BaseRowCount Locale = $Locale } if (-not $systemPrompt) { Write-PSFMessage -Level Warning -Message ($script:strings.'Prompt.ResolveFailed' -f 'schema-analysis') return $null } $userMessage = "Analyze this schema with sample data and produce per-table generation notes:`n`n$relationshipGraph`n`n$schemaText" Write-PSFMessage -Level Verbose -Message ($script:strings.'AI.SchemaAnalysisRequesting' -f $SchemaModel.TableCount) $response = Invoke-SldgAIRequest -SystemPrompt $systemPrompt -UserMessage $userMessage -Purpose 'schema-analysis' if (-not $response) { Write-PSFMessage -Level Warning -Message $script:strings.'AI.SchemaAnalysisNoResponse' return $null } # Parse JSON response $jsonText = $response if ($jsonText -match '```(?:json)?\s*\n?([\s\S]*?)\n?```') { $jsonText = $Matches[1] } elseif ($jsonText -match '(\{[\s\S]*\})') { $jsonText = $Matches[1] } # Sanitize common AI JSON issues $jsonText = $jsonText -replace ',\s*"\.{2,}"', '' $jsonText = $jsonText -replace '"\.{2,}"\s*,?', '' $jsonText = $jsonText -replace '(?<=[\]\}]),\s*(?=[\]\}])', '' try { $parsed = $jsonText | ConvertFrom-Json -ErrorAction Stop if (-not $parsed.tableNotes) { Write-PSFMessage -Level Warning -Message ($script:strings.'AI.SchemaAnalysisFailed' -f 'AI response missing required "tableNotes" property') return $null } $notes = @{} foreach ($prop in $parsed.tableNotes.PSObject.Properties) { $notes[$prop.Name] = [string]$prop.Value } Write-PSFMessage -Level Verbose -Message ($script:strings.'AI.SchemaAnalysisReceived' -f $notes.Count) $notes } catch { Write-PSFMessage -Level Warning -Message ($script:strings.'AI.SchemaAnalysisFailed' -f $_) $null } } |