microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/1637-d-skill-paths

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/evals/Invoke-ContentModeration.ps1

199lines · 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 = (git rev-parse --show-toplevel 2>$null) ?? $PSScriptRoot
73)
74
75$ErrorActionPreference = 'Stop'
76
77Import-Module (Join-Path $PSScriptRoot 'Modules/ModerationRunner.psm1') -Force
78
79#region Main Execution
80
81if ($MyInvocation.InvocationName -ne '.') {
82 # Validate mutually exclusive parameters
83 $hasFileList = $null -ne $FileList -and $FileList.Count -gt 0
84 $recordsBound = $PSBoundParameters.ContainsKey('Records')
85 $hasRecords = $null -ne $Records -and $Records.Count -gt 0
86
87 # Use [Console]::Error.WriteLine + exit instead of Write-Error to bypass
88 # $ErrorActionPreference = 'Stop' (which would terminate with exit 1 before
89 # reaching the explicit exit code).
90 if ($hasFileList -and $recordsBound) {
91 [Console]::Error.WriteLine("-FileList and -Records are mutually exclusive")
92 exit 2
93 }
94
95 if (-not $hasFileList -and -not $recordsBound) {
96 [Console]::Error.WriteLine("Either -FileList or -Records is required")
97 exit 2
98 }
99
100 # Build records from FileList if provided
101 if ($hasFileList) {
102 Write-Verbose "Building records from $($FileList.Count) files"
103 $Records = ConvertTo-ModerationRecords -FileList $FileList -RepoRoot $RepoRoot
104 }
105
106 if (-not $hasRecords -and -not $hasFileList) {
107 # Records explicitly bound as empty — write empty output and exit 0
108 Write-Warning "No records to moderate for scope: $Scope"
109 if (-not $OutFile) {
110 $OutFile = Join-Path $RepoRoot "logs/moderation-$Scope.json"
111 }
112 $emptyOutput = @{
113 records = @()
114 summary = @{ total = 0; flaggedCount = 0 }
115 }
116 $OutFile | Split-Path -Parent | ForEach-Object { New-Item -ItemType Directory -Force -Path $_ | Out-Null }
117 $emptyOutput | ConvertTo-Json -Depth 10 | Set-Content -Path $OutFile -Encoding utf8NoBOM
118 exit 0
119 }
120
121 if ($Records.Count -eq 0) {
122 Write-Warning "No records to moderate for scope: $Scope"
123 # Write empty output file
124 if (-not $OutFile) {
125 $OutFile = Join-Path $RepoRoot "logs/moderation-$Scope.json"
126 }
127 $emptyOutput = @{
128 records = @()
129 summary = @{ total = 0; flaggedCount = 0 }
130 }
131 $OutFile | Split-Path -Parent | ForEach-Object { New-Item -ItemType Directory -Force -Path $_ | Out-Null }
132 $emptyOutput | ConvertTo-Json -Depth 10 | Set-Content -Path $OutFile -Encoding utf8NoBOM
133 exit 0
134 }
135
136 # Create temp input file
137 $tempInput = $null
138 try {
139 $tempInput = New-ModerationInputFile -Records $Records
140 Write-Verbose "Created input file: $tempInput"
141
142 # Resolve output path
143 if (-not $OutFile) {
144 $OutFile = Join-Path $RepoRoot "logs/moderation-$Scope.json"
145 }
146 $OutFile = [System.IO.Path]::GetFullPath($OutFile, $RepoRoot)
147 $outDir = Split-Path $OutFile -Parent
148 if (-not (Test-Path $outDir)) {
149 New-Item -ItemType Directory -Force -Path $outDir | Out-Null
150 }
151
152 # Invoke moderate.py
153 $moderatePy = Join-Path $PSScriptRoot 'moderation/moderate.py'
154 if (-not (Test-Path $moderatePy)) {
155 Write-Error "moderate.py not found at $moderatePy"
156 exit 2
157 }
158
159 Write-Verbose "Invoking moderate.py: threshold=$Threshold model=$Model"
160 $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
161 if (-not $pythonCmd) {
162 Write-Error "python not found in PATH; ensure Python 3.11+ is installed"
163 exit 2
164 }
165
166 & python $moderatePy --input $tempInput --threshold $Threshold --model $Model --output $OutFile
167
168 if ($LASTEXITCODE -ne 0) {
169 Write-Error "moderate.py exited with code $LASTEXITCODE"
170 # Check if output exists and surface errors
171 if (Test-Path $OutFile) {
172 $flagged = Test-ModerationOutput -OutputPath $OutFile
173 if ($flagged) {
174 Write-Error "Content moderation failed for scope: $Scope"
175 exit 1
176 }
177 }
178 exit $LASTEXITCODE
179 }
180
181 # Surface any flags
182 $flagged = Test-ModerationOutput -OutputPath $OutFile
183 if ($flagged) {
184 Write-Error "Content moderation failed for scope: $Scope"
185 exit 1
186 }
187
188 Write-Host "Content moderation passed for scope: $Scope ($($Records.Count) records)"
189 exit 0
190 }
191 finally {
192 # Clean up temp file
193 if ($tempInput -and (Test-Path $tempInput)) {
194 Remove-Item $tempInput -Force -ErrorAction SilentlyContinue
195 }
196 }
197}
198
199#endregion Main Execution
200