Files
Omika/scripts/Completer-Packs-Langues.ps1
T
2026-10-23 18:46:50 +02:00

265 lines
11 KiB
PowerShell

# ============================================================
# Omika - Completion des packs de langue deja exportes
# ============================================================
# Les packs exportes avant l'introduction du vocabulaire de modele contiennent
# quatre fichiers : encoder_model.onnx, decoder_model.onnx, source.spm,
# target.spm. Il en manque deux, sans lesquels le moteur ne peut pas traduire
# correctement :
#
# vocab.json - les identifiants REELLEMENT attendus par le modele. Le .spm
# numerote ses pieces dans un ordre qui lui est propre : sur
# opus-mt-es-fr, la piece <U+2581>Ho vaut 5293 pour le .spm mais
# 584 pour le modele. Envoyer les identifiants du .spm revient a
# faire lire un autre texte, sans la moindre erreur levee.
# config.json - l'amorce et la fin du decodeur (74821 et 0 sur opus-mt-es-fr),
# que le .spm ne contient pas non plus.
#
# Ces deux fichiers pesent quelques mega-octets : rien n'est reexporte, les poids
# deja sur le disque (23 Gio) restent valables.
#
# GARDE-FOU : le modele d'origine n'est pas note dans le pack. Il est deduit du
# code de langue et du sens (es + versFrancais -> opus-mt-es-fr), puis VERIFIE :
# le source.spm telecharge doit etre identique, octet pour octet, a celui du
# pack. Sans cette comparaison, on attacherait le vocabulaire d'un autre modele
# - exactement le defaut que ce script repare, en pire.
#
# Usage :
# .\scripts\Completer-Packs-Langues.ps1 -Verifier
# .\scripts\Completer-Packs-Langues.ps1
# .\scripts\Completer-Packs-Langues.ps1 -Codes "es,pt,it"
# ============================================================
[CmdletBinding()]
param(
# Racine des packs. Par defaut : <racine du depot>\packs
[string]$RacinePacks = '',
# N'ecrit rien : dit seulement quels packs sont deja alignes ou non.
[switch]$Verifier,
# Codes de langue a traiter (ex. "es,pt"). Vide = tous les dossiers trouves.
[string]$Codes = ''
)
$ErrorActionPreference = 'Stop'
# --- Sortie ----------------------------------------------------------------
function Write-Etape { param([string]$Message) Write-Host ''; Write-Host $Message -ForegroundColor Cyan }
function Write-Info { param([string]$Message) Write-Host (' ' + $Message) }
function Write-Ok { param([string]$Message) Write-Host (' [OK] ' + $Message) -ForegroundColor Green }
function Write-Alerte{ param([string]$Message) Write-Host (' [ALERTE] ' + $Message) -ForegroundColor Yellow }
function Write-Ecart { param([string]$Message) Write-Host (' [ECART] ' + $Message) -ForegroundColor DarkYellow }
function Stop-AvecMessage {
param([string]$Message)
Write-Host ''
Write-Host '=== ARRET ===' -ForegroundColor Red
Write-Host $Message -ForegroundColor Red
exit 1
}
# --- Logique ---------------------------------------------------------------
# Nom du depot HuggingFace correspondant a un pack. Convention de l'exporteur :
# versFrancais = <langue> -> francais, depuisFrancais = francais -> <langue>.
function Get-NomModele {
param(
[Parameter(Mandatory = $true)][string]$Code,
[Parameter(Mandatory = $true)][string]$Sens,
[string]$Auteur = 'Helsinki-NLP'
)
$codeNormalise = $Code.Trim().ToLowerInvariant()
if ($Sens -eq 'versFrancais') {
return ('{0}/opus-mt-{1}-fr' -f $Auteur, $codeNormalise)
}
return ('{0}/opus-mt-fr-{1}' -f $Auteur, $codeNormalise)
}
# Empreinte SHA-256 en minuscules, comparable telle quelle.
function Get-Empreinte {
param([Parameter(Mandatory = $true)][string]$Chemin)
return (Get-FileHash -LiteralPath $Chemin -Algorithm SHA256).Hash.ToLowerInvariant()
}
# Inventaire des couples (code, sens) presents sur le disque.
function Get-PacksPresents {
param(
[Parameter(Mandatory = $true)][string]$Racine,
[string[]]$CodesFiltres = @()
)
$presents = New-Object 'System.Collections.Generic.List[object]'
if (-not (Test-Path -LiteralPath $Racine -PathType Container)) {
return $presents
}
foreach ($dossierLangue in @(Get-ChildItem -LiteralPath $Racine -Directory | Sort-Object -Property Name)) {
$code = $dossierLangue.Name
# Dossiers techniques (.hf-cache) et fichiers d'essai : jamais des langues.
if ($code.StartsWith('.')) { continue }
if ($CodesFiltres.Count -gt 0 -and $CodesFiltres -notcontains $code) { continue }
foreach ($dossierSens in @(Get-ChildItem -LiteralPath $dossierLangue.FullName -Directory | Sort-Object -Property Name)) {
$sens = $dossierSens.Name
if ($sens -ne 'versFrancais' -and $sens -ne 'depuisFrancais') { continue }
# Un pack FAMILIAL (modele multilingue) range ses poids dans
# <code>\commun\ et ne laisse qu'un modele.json par sens : il n'a
# aucun source.spm par sens, donc RIEN a aligner. Sans ce garde-fou
# on lui cherchait une paire bilingue qui n'existe pas et on le
# declarait « ecarte » a tort.
$spm = Join-Path $dossierSens.FullName 'source.spm'
if (-not (Test-Path -LiteralPath $spm -PathType Leaf)) { continue }
$presents.Add([pscustomobject]@{
Code = $code
Sens = $sens
Dossier = $dossierSens.FullName
Source = $spm
})
}
}
return $presents
}
# Complete un pack. $Recuperer recoit (modele, fichier, destination) et doit
# renvoyer $true si le fichier a ete obtenu. Il est injecte pour que les tests
# s'executent sans reseau.
function Invoke-Completion {
param(
[Parameter(Mandatory = $true)][string]$Racine,
[Parameter(Mandatory = $true)][scriptblock]$Recuperer,
[switch]$SansEcrire,
[string[]]$CodesFiltres = @()
)
$bilan = [pscustomobject]@{
Packs = 0
Alignes = 0
Ecartes = 0
Echecs = 0
Ecrits = 0
}
$packs = @(Get-PacksPresents -Racine $Racine -CodesFiltres $CodesFiltres)
if ($packs.Count -eq 0) {
Write-Alerte ("aucun pack trouve sous {0}" -f $Racine)
return $bilan
}
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-completion-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
$null = New-Item -ItemType Directory -Path $bac -Force
try {
foreach ($pack in $packs) {
$bilan.Packs++
$modele = Get-NomModele -Code $pack.Code -Sens $pack.Sens
$nomCourt = '{0}/{1}' -f $pack.Code, $pack.Sens
$spmTelecharge = Join-Path $bac ('{0}-{1}-source.spm' -f $pack.Code, $pack.Sens)
$vocabTelecharge = Join-Path $bac ('{0}-{1}-vocab.json' -f $pack.Code, $pack.Sens)
$configTelecharge = Join-Path $bac ('{0}-{1}-config.json' -f $pack.Code, $pack.Sens)
$obtenus = $true
foreach ($paire in @(
@{ Fichier = 'source.spm'; Destination = $spmTelecharge },
@{ Fichier = 'vocab.json'; Destination = $vocabTelecharge },
@{ Fichier = 'config.json'; Destination = $configTelecharge })) {
if (-not (& $Recuperer $modele $paire.Fichier $paire.Destination)) {
$obtenus = $false
Write-Alerte ("{0} : {1} introuvable pour {2}" -f $nomCourt, $paire.Fichier, $modele)
break
}
}
if (-not $obtenus) {
$bilan.Echecs++
continue
}
# LE garde-fou : le modele deduit doit etre celui du pack.
$empreinteLocale = Get-Empreinte -Chemin $pack.Source
$empreinteDistante = Get-Empreinte -Chemin $spmTelecharge
if ($empreinteLocale -ne $empreinteDistante) {
Write-Ecart ("{0} : {1} n'est PAS le modele de ce pack (source.spm different) - non complete" -f $nomCourt, $modele)
$bilan.Ecartes++
continue
}
$bilan.Alignes++
if ($SansEcrire) {
Write-Ok ("{0} : aligne sur {1}" -f $nomCourt, $modele)
continue
}
Copy-Item -LiteralPath $vocabTelecharge -Destination (Join-Path $pack.Dossier 'vocab.json') -Force
Copy-Item -LiteralPath $configTelecharge -Destination (Join-Path $pack.Dossier 'config.json') -Force
$bilan.Ecrits++
Write-Ok ("{0} : vocabulaire et configuration ajoutes ({1})" -f $nomCourt, $modele)
}
}
finally {
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
}
return $bilan
}
# --- Execution -------------------------------------------------------------
# Les tests dot-sourcent ce fichier pour appeler les fonctions : dans ce cas,
# rien ne s'execute. Seul un lancement direct declenche le traitement.
if ($MyInvocation.InvocationName -ne '.') {
if ([string]::IsNullOrWhiteSpace($RacinePacks)) {
$RacinePacks = Join-Path (Split-Path -Parent $PSScriptRoot) 'packs'
}
$codesFiltres = @()
if (-not [string]::IsNullOrWhiteSpace($Codes)) {
$codesFiltres = @($Codes -split '[,;]' | ForEach-Object { $_.Trim().ToLowerInvariant() } | Where-Object { $_ })
}
Write-Host ''
Write-Host '=== Omika - Completion des packs de langue ===' -ForegroundColor Cyan
Write-Info ('Racine des packs : {0}' -f $RacinePacks)
Write-Info ('Mode : {0}' -f $(if ($Verifier) { 'verification seule (aucune ecriture)' } else { 'ecriture' }))
if ($codesFiltres.Count -gt 0) { Write-Info ('Langues : {0}' -f ($codesFiltres -join ', ')) }
# Recuperation reelle : HuggingFace, sans jeton (les depots sont publics).
$recuperer = {
param($modele, $fichier, $destination)
$uri = 'https://huggingface.co/{0}/resolve/main/{1}' -f $modele, $fichier
try {
Invoke-WebRequest -Uri $uri -OutFile $destination -UseBasicParsing -ErrorAction Stop
return (Test-Path -LiteralPath $destination -PathType Leaf)
}
catch {
return $false
}
}
$bilan = Invoke-Completion -Racine $RacinePacks -Recuperer $recuperer -SansEcrire:$Verifier -CodesFiltres $codesFiltres
Write-Etape '=== RESULTAT ==='
Write-Info ('Packs examines : {0}' -f $bilan.Packs)
Write-Info ('Alignes : {0}' -f $bilan.Alignes)
Write-Info ('Completes : {0}' -f $bilan.Ecrits)
Write-Info ('Ecartes (autre modele) : {0}' -f $bilan.Ecartes)
Write-Info ('Echecs : {0}' -f $bilan.Echecs)
if ($bilan.Echecs -gt 0) {
Stop-AvecMessage ("{0} pack(s) n'ont pas pu etre completes (reseau ou depots introuvables).`n" -f $bilan.Echecs +
" Relancer la MEME commande : les packs deja completes ne sont pas retraites.")
}
if ($bilan.Ecartes -gt 0) {
Write-Alerte 'Les packs ecartes viennent d''une autre famille de modeles que celle deduite du code.'
Write-Alerte 'Ils doivent etre reexportes avec scripts\Exporter-Packs-Langues.ps1 (le modele d''origine y sera note).'
}
exit 0
}