microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/1873-devcontainer

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/evals/Invoke-ContentModeration.ps1

203lines · modecode

1#!/usr/bin/env pwsh
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4#Requires -Version 7.0
5
6<#
7.SYNOPSIS
8 PowerShell wrapper for moderate.py content moderation CLI.
9
10.DESCRIPTION
11 Builds JSON-lines input from a file list or inline record array, invokes
12 moderate.py with configurable threshold and model, and surfaces structured
13 error messages for flagged content. Writes output to logs/ and exits with
14 code 1 when any record triggers a flag.
15
16.PARAMETER FileList
17 Array of file paths to moderate. Mutually exclusive with -Records.
18
19.PARAMETER Records
20 Array of hashtables with 'id' and 'text' keys. Mutually exclusive with
21 -FileList.
22
23.PARAMETER Scope
24 Scope identifier for output filename (moderation-<scope>.json).
25
26.PARAMETER Threshold
27 Toxicity threshold (0.0-1.0). Defaults to 0.5.
28
29.PARAMETER Model
30 Detoxify model variant: original, unbiased, multilingual. Defaults to
31 unbiased.
32
33.PARAMETER OutFile
34 Output path for moderation results. Defaults to logs/moderation-<Scope>.json.
35
36.PARAMETER RepoRoot
37 Repository root directory. Defaults to git repo root or script directory.
38
39.EXAMPLE
40 ./Invoke-ContentModeration.ps1 -FileList @('doc1.md', 'doc2.md') -Scope 'corpus'
41
42.EXAMPLE
43 $records = @(@{ id = 'rec1'; text = 'Test content' })
44 ./Invoke-ContentModeration.ps1 -Records $records -Scope 'input-artifact-1'
45
46.NOTES
47 Runs via: npm run eval:moderate
48#>
49[CmdletBinding()]
50param(
51 [Parameter(Mandatory = $false)]
52 [string[]]$FileList,
53
54 [Parameter(Mandatory = $false)]
55 [AllowEmptyCollection()]
56 [hashtable[]]$Records,
57
58 [Parameter(Mandatory = $true)]
59 [string]$Scope,
60
61 [Parameter(Mandatory = $false)]
62 [double]$Threshold = 0.5,
63
64 [Parameter(Mandatory = $false)]
65 [ValidateSet('original', 'unbiased', 'multilingual')]
66 [string]$Model = 'unbiased',
67
68 [Parameter(Mandatory = $false)]
69 [string]$OutFile,
70
71 [Parameter(Mandatory = $false)]
72 [string]$RepoRoot = $(
73 $detectedRoot = git rev-parse --show-toplevel 2>$null
74 if ($detectedRoot) { $detectedRoot } else { $PSScriptRoot }
75 )
76)
77
78Set-StrictMode -Version Latest
79$ErrorActionPreference = 'Stop'
80
81Import-Module (Join-Path $PSScriptRoot 'Modules/ModerationRunner.psm1') -Force
82
83#region Main Execution
84
85if ($MyInvocation.InvocationName -ne '.') {
86 # Validate mutually exclusive parameters
87 $hasFileList = $null -ne $FileList -and $FileList.Count -gt 0
88 $recordsBound = $PSBoundParameters.ContainsKey('Records')
89 $hasRecords = $null -ne $Records -and $Records.Count -gt 0
90
91 # Use [Console]::Error.WriteLine + exit instead of Write-Error to bypass
92 # $ErrorActionPreference = 'Stop' (which would terminate with exit 1 before
93 # reaching the explicit exit code).
94 if ($hasFileList -and $recordsBound) {
95 [Console]::Error.WriteLine("-FileList and -Records are mutually exclusive")
96 exit 2
97 }
98
99 if (-not $hasFileList -and -not $recordsBound) {
100 [Console]::Error.WriteLine("Either -FileList or -Records is required")
101 exit 2
102 }
103
104 # Build records from FileList if provided
105 if ($hasFileList) {
106 Write-Verbose "Building records from $($FileList.Count) files"
107 $Records = ConvertTo-ModerationRecords -FileList $FileList -RepoRoot $RepoRoot
108 }
109
110 if (-not $hasRecords -and -not $hasFileList) {
111 # Records explicitly bound as empty — write empty output and exit 0
112 Write-Warning "No records to moderate for scope: $Scope"
113 if (-not $OutFile) {
114 $OutFile = Join-Path $RepoRoot "logs/moderation-$Scope.json"
115 }
116 $emptyOutput = @{
117 records = @()
118 summary = @{ total = 0; flaggedCount = 0 }
119 }
120 $OutFile | Split-Path -Parent | ForEach-Object { New-Item -ItemType Directory -Force -Path $_ | Out-Null }
121 $emptyOutput | ConvertTo-Json -Depth 10 | Set-Content -Path $OutFile -Encoding utf8NoBOM
122 exit 0
123 }
124
125 if ($Records.Count -eq 0) {
126 Write-Warning "No records to moderate for scope: $Scope"
127 # Write empty output file
128 if (-not $OutFile) {
129 $OutFile = Join-Path $RepoRoot "logs/moderation-$Scope.json"
130 }
131 $emptyOutput = @{
132 records = @()
133 summary = @{ total = 0; flaggedCount = 0 }
134 }
135 $OutFile | Split-Path -Parent | ForEach-Object { New-Item -ItemType Directory -Force -Path $_ | Out-Null }
136 $emptyOutput | ConvertTo-Json -Depth 10 | Set-Content -Path $OutFile -Encoding utf8NoBOM
137 exit 0
138 }
139
140 # Create temp input file
141 $tempInput = $null
142 try {
143 $tempInput = New-ModerationInputFile -Records $Records
144 Write-Verbose "Created input file: $tempInput"
145
146 # Resolve output path
147 if (-not $OutFile) {
148 $OutFile = Join-Path $RepoRoot "logs/moderation-$Scope.json"
149 }
150 $OutFile = [System.IO.Path]::GetFullPath($OutFile, $RepoRoot)
151 $outDir = Split-Path $OutFile -Parent
152 if (-not (Test-Path $outDir)) {
153 New-Item -ItemType Directory -Force -Path $outDir | Out-Null
154 }
155
156 # Invoke moderate.py
157 $moderatePy = Join-Path $PSScriptRoot 'moderation/moderate.py'
158 if (-not (Test-Path $moderatePy)) {
159 [Console]::Error.WriteLine("moderate.py not found at $moderatePy")
160 exit 2
161 }
162
163 Write-Verbose "Invoking moderate.py: threshold=$Threshold model=$Model"
164 $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
165 if (-not $pythonCmd) {
166 [Console]::Error.WriteLine("python not found in PATH; ensure Python 3.11+ is installed")
167 exit 2
168 }
169
170 & python $moderatePy --input $tempInput --threshold $Threshold --model $Model --output $OutFile
171
172 if ($LASTEXITCODE -ne 0) {
173 [Console]::Error.WriteLine("moderate.py exited with code $LASTEXITCODE")
174 # Check if output exists and surface errors
175 if (Test-Path $OutFile) {
176 $flagged = Test-ModerationOutput -OutputPath $OutFile
177 if ($flagged) {
178 [Console]::Error.WriteLine("Content moderation failed for scope: $Scope")
179 exit 1
180 }
181 }
182 exit $LASTEXITCODE
183 }
184
185 # Surface any flags
186 $flagged = Test-ModerationOutput -OutputPath $OutFile
187 if ($flagged) {
188 [Console]::Error.WriteLine("Content moderation failed for scope: $Scope")
189 exit 1
190 }
191
192 Write-Host "Content moderation passed for scope: $Scope ($($Records.Count) records)"
193 exit 0
194 }
195 finally {
196 # Clean up temp file
197 if ($tempInput -and (Test-Path $tempInput)) {
198 Remove-Item $tempInput -Force -ErrorAction SilentlyContinue
199 }
200 }
201}
202
203#endregion Main Execution
204