Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Omika.LanguesProbe;
|
||||
|
||||
/// <summary>
|
||||
/// Omika.LanguesProbe — vérifie un couple de traduction hors ligne (FR ⇄ ES)
|
||||
/// sur le poste de développement, à partir des fichiers réels du pack.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Pourquoi cet outil.</b> Tant que le symbole <c>OMIKA_ONNX_RUNTIME</c> n'est
|
||||
/// pas défini, le moteur d'inférence n'est embarqué dans aucune tête de
|
||||
/// livraison : contrôler un <c>vocab.spm</c> et un <c>*.onnx</c> réels
|
||||
/// (identifiants des jetons techniques, normalisation, aller-retour du
|
||||
/// découpage, traduction complète) supposait un déploiement sur téléphone. Ici
|
||||
/// les fichiers sont lus directement sur le disque : c'est le véhicule
|
||||
/// d'exécution qui permet de décider de l'activation du moteur à partir de
|
||||
/// mesures, et non de suppositions.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Aucun paquet NuGet supplémentaire</b> : l'outil n'utilise que du code déjà
|
||||
/// présent dans <c>Omika.App.Local</c>, qui compile et s'exécute sans ONNX
|
||||
/// Runtime.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Codes de sortie</b> : 0 succès, 1 erreur d'usage (argument manquant,
|
||||
/// option ou commande inconnue), 2 opération en échec (fichier absent,
|
||||
/// vocabulaire refusé, traduction échouée).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Version lue dans les métadonnées d'assembly, elle-même issue de
|
||||
/// <c>Directory.Build.props</c> : un littéral ici dériverait de la version de
|
||||
/// la solution sans que personne ne s'en aperçoive.
|
||||
/// </summary>
|
||||
private static readonly string VersionOutil =
|
||||
typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
|
||||
?? typeof(Program).Assembly.GetName().Version?.ToString()
|
||||
?? "version inconnue";
|
||||
|
||||
// Motif des chemins absolus (Windows « C:\a\b » comme Unix « /a/b »), réduits au seul nom de
|
||||
// fichier dans les messages : un diagnostic ne doit pas divulguer l'arborescence de la machine.
|
||||
private static readonly Regex CheminAbsolu = new(@"(?:[A-Za-z]:)?(?:[\\/][^\\/\s""']+){2,}", RegexOptions.Compiled);
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Executer(args);
|
||||
}
|
||||
catch (AideDemandeeException)
|
||||
{
|
||||
// « --aide » demandé au milieu d'une commande : ce n'est pas une erreur.
|
||||
AfficherAide();
|
||||
return CodesSortie.Succes;
|
||||
}
|
||||
catch (ErreurUsageException ex)
|
||||
{
|
||||
Console.Error.WriteLine($"ERREUR D'USAGE : {ex.Message}");
|
||||
Console.Error.WriteLine("Utilisez « --aide » pour la liste des commandes.");
|
||||
return CodesSortie.ErreurUsage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Aucune trace technique ne sort d'ici : le message d'une exception .NET peut contenir
|
||||
// le chemin absolu d'un fichier. Le nom de fichier seul suffit à agir.
|
||||
Console.Error.WriteLine($"ÉCHEC : {MessageSansChemin(ex.Message)}");
|
||||
return CodesSortie.OperationEchouee;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nom de fichier seul, pour tout message destiné à l'écran. Retourne une
|
||||
/// mention neutre plutôt que le chemin brut quand le chemin n'a pas de nom
|
||||
/// exploitable (chemin racine, chemin terminé par un séparateur).
|
||||
/// </summary>
|
||||
internal static string NomFichier(string chemin)
|
||||
{
|
||||
string nom = Path.GetFileName(chemin.TrimEnd('\\', '/'));
|
||||
return string.IsNullOrEmpty(nom) ? "(élément sans nom)" : nom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remplace les chemins absolus d'un message par leur seul nom de fichier.
|
||||
/// Les messages de ce dépôt évitent déjà les chemins, mais ceux du framework
|
||||
/// (<c>IOException</c>, <c>FileNotFoundException</c>) n'en donnent aucune
|
||||
/// garantie : le filtre est donc appliqué à la frontière, une seule fois.
|
||||
/// </summary>
|
||||
internal static string MessageSansChemin(string message)
|
||||
=> CheminAbsolu.Replace(message, m => Path.GetFileName(m.Value));
|
||||
|
||||
/// <summary>
|
||||
/// Rappel actionnable quand aucun moteur d'inférence n'est utilisable : sans
|
||||
/// lui, l'échec serait incompréhensible alors que c'est l'état attendu de la
|
||||
/// machine de développement.
|
||||
/// </summary>
|
||||
internal static void AfficherMoteurAbsent()
|
||||
{
|
||||
Console.Error.WriteLine();
|
||||
Console.Error.WriteLine(" La traduction ne peut pas être calculée : le moteur d'inférence n'est pas");
|
||||
Console.Error.WriteLine(" compilé dans Omika.App.Local (symbole OMIKA_ONNX_RUNTIME absent).");
|
||||
Console.Error.WriteLine(" → Activer le moteur : docs\\VALIDATION_TRADUCTION_ONNX.md");
|
||||
Console.Error.WriteLine(" (procédure et état actuels : docs\\TRADUCTION_HORS_LIGNE_MOBILE.md, §7)");
|
||||
Console.Error.WriteLine(" → « inspecter » et « encoder » restent utilisables sans moteur.");
|
||||
}
|
||||
|
||||
private static int Executer(string[] args)
|
||||
{
|
||||
PreparerConsole();
|
||||
AfficherEnTete();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
// L'aide est la réponse utile, mais l'appel est incomplet : le code de sortie le dit,
|
||||
// pour qu'un script distingue « commande absente » de « commande réussie ».
|
||||
AfficherAide();
|
||||
return CodesSortie.ErreurUsage;
|
||||
}
|
||||
|
||||
string commande = args[0].Trim().ToLowerInvariant();
|
||||
string[] reste = args[1..];
|
||||
|
||||
switch (commande)
|
||||
{
|
||||
case "--aide" or "-h" or "aide":
|
||||
AfficherAide();
|
||||
return CodesSortie.Succes;
|
||||
case "inspecter":
|
||||
return InspectionVocabulaire.Inspecter(reste);
|
||||
case "encoder":
|
||||
return InspectionVocabulaire.Encoder(reste);
|
||||
case "traduire":
|
||||
return TraductionEssai.Traduire(reste);
|
||||
default:
|
||||
Console.Error.WriteLine($"ERREUR D'USAGE : commande inconnue « {args[0]} ».");
|
||||
AfficherAide();
|
||||
return CodesSortie.ErreurUsage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sortie en UTF-8 : les messages sont en français et l'affichage des pièces
|
||||
/// utilise le marqueur d'espace ▁ du modèle. Sans cela, une console Windows
|
||||
/// restée sur une page de codes héritée rendrait des caractères de
|
||||
/// remplacement — un diagnostic illisible ne sert à rien. Un échec est
|
||||
/// ignoré : une sortie redirigée n'a pas de console à reconfigurer.
|
||||
/// </summary>
|
||||
private static void PreparerConsole()
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Aucune console interactive : l'encodage demandé par l'appelant fait foi.
|
||||
}
|
||||
}
|
||||
|
||||
private static void AfficherEnTete()
|
||||
=> Console.WriteLine(
|
||||
$"Omika.LanguesProbe {VersionOutil} — validation d'un couple de traduction hors ligne (FR ⇄ ES), "
|
||||
+ "sans déploiement." + Environment.NewLine);
|
||||
|
||||
private static void AfficherAide()
|
||||
{
|
||||
Console.WriteLine(
|
||||
"""
|
||||
COMMANDES
|
||||
inspecter <vocab.spm>
|
||||
Lit un vocabulaire SentencePiece réel et affiche : nombre de pièces,
|
||||
identifiants des jetons <unk> / <s> / </s> / <pad> (« absent » vaut -1),
|
||||
profil de normalisation, repli octet, avertissements du tokeniseur, puis un
|
||||
aller-retour texte → identifiants → texte sur des phrases françaises et
|
||||
espagnoles. Ne nécessite AUCUN moteur ONNX.
|
||||
|
||||
encoder <vocab.spm> <texte…>
|
||||
Découpe un texte et affiche chaque identifiant avec la pièce qu'il porte, dans
|
||||
l'ordre. Sert à comparer avec un tokeniseur de référence (sentencepiece,
|
||||
transformers) pour localiser une divergence de découpage.
|
||||
Le texte peut être cité entre guillemets ou donné en plusieurs arguments.
|
||||
|
||||
traduire --dossier <dossierDuPack> --texte "<texte>" [--sens <sens>] [--code <code>]
|
||||
Décrit le pack RÉELLEMENT présent dans <dossierDuPack> (fichiers *.spm et
|
||||
*.onnx), puis exécute le pipeline complet de OnnxTranslationService : résultat,
|
||||
temps de chargement du vocabulaire, temps de chargement de chaque modèle et
|
||||
durée de la traduction. Le dossier est traité comme « langues/es/<sens> » :
|
||||
ses fichiers sont lus tels quels, rien n'y est écrit ni modifié.
|
||||
--sens versFrancais (défaut) | depuisFrancais
|
||||
--code code de langue du pack (défaut : es)
|
||||
--texte texte à traduire (obligatoire)
|
||||
|
||||
--aide, -h
|
||||
Affiche cette aide.
|
||||
|
||||
CODES DE SORTIE
|
||||
0 succès
|
||||
1 erreur d'usage (argument manquant, option ou commande inconnue)
|
||||
2 opération en échec (fichier absent, vocabulaire refusé, traduction échouée)
|
||||
|
||||
SANS MOTEUR ONNX RUNTIME
|
||||
Le moteur n'est compilé que sous le symbole OMIKA_ONNX_RUNTIME (voir
|
||||
docs\VALIDATION_TRADUCTION_ONNX.md ; état actuel : docs\TRADUCTION_HORS_LIGNE_MOBILE.md,
|
||||
§7). « inspecter » et « encoder » fonctionnent sans lui ; « traduire » explique alors
|
||||
que la traduction est indisponible, sans jamais afficher de trace technique.
|
||||
|
||||
EXEMPLES
|
||||
Omika.LanguesProbe inspecter "C:\packs\es\versFrancais\vocab.spm"
|
||||
Omika.LanguesProbe encoder "C:\packs\es\versFrancais\vocab.spm" "Bonjour, comment allez-vous ?"
|
||||
Omika.LanguesProbe traduire --dossier "C:\packs\es\versFrancais" --texte "La factura se enviará mañana."
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Codes de sortie du programme, énoncés aussi par <c>--aide</c>.</summary>
|
||||
internal static class CodesSortie
|
||||
{
|
||||
internal const int Succes = 0;
|
||||
internal const int ErreurUsage = 1;
|
||||
internal const int OperationEchouee = 2;
|
||||
}
|
||||
Reference in New Issue
Block a user