diff --git a/src/JD.AI.Telemetry/Extensions/TelemetryServiceExtensions.cs b/src/JD.AI.Telemetry/Extensions/TelemetryServiceExtensions.cs index 2b5652b5..2262f3f2 100644 --- a/src/JD.AI.Telemetry/Extensions/TelemetryServiceExtensions.cs +++ b/src/JD.AI.Telemetry/Extensions/TelemetryServiceExtensions.cs @@ -98,11 +98,13 @@ private static void ConfigureTraceExporter(TracerProviderBuilder tracing, Teleme break; case "zipkin": +#pragma warning disable CS0618 // OpenTelemetry deprecated Zipkin exporter; keep existing config support until migrated. tracing.AddZipkinExporter(o => { if (TryParseEndpoint(endpoint, out var uri)) o.Endpoint = uri; }); +#pragma warning restore CS0618 break; default: // "console" diff --git a/tools/JD.AI.Workflows.Training/AiTrainingDataSynthesizer.cs b/tools/JD.AI.Workflows.Training/AiTrainingDataSynthesizer.cs index b5a72deb..a81c8d73 100644 --- a/tools/JD.AI.Workflows.Training/AiTrainingDataSynthesizer.cs +++ b/tools/JD.AI.Workflows.Training/AiTrainingDataSynthesizer.cs @@ -12,9 +12,9 @@ public sealed class AiTrainingDataSynthesizer : IDisposable private readonly string _model; private int _generated; - public AiTrainingDataSynthesizer(string model = "qwen3.5:9b") + public AiTrainingDataSynthesizer(string model = "qwen3.5:9b", string ollamaHost = "http://localhost:11434") { - _ollama = new OllamaClient(model); + _ollama = new OllamaClient(model, ollamaHost); _model = model; } diff --git a/tools/JD.AI.Workflows.Training/Program.cs b/tools/JD.AI.Workflows.Training/Program.cs index da9975c0..a1362aba 100644 --- a/tools/JD.AI.Workflows.Training/Program.cs +++ b/tools/JD.AI.Workflows.Training/Program.cs @@ -44,12 +44,86 @@ public static async Task Main(string[] args) ? args[outputArgIdx + 1] : "../../src/JD.AI.Workflows/Models/intent_classifier.zip"; + var ollamaGenerateArgIdx = Array.IndexOf(args, "--ollama-generate"); + int? ollamaGenerateCount = ollamaGenerateArgIdx >= 0 && ollamaGenerateArgIdx + 1 < args.Length + ? int.Parse(args[ollamaGenerateArgIdx + 1], System.Globalization.CultureInfo.InvariantCulture) + : null; + var ollamaValidate = args.Contains("--ollama-validate"); + if (benchmark) { RunBenchmark(); return 0; } + // ── Ollama-synthesized training data generation ────────────────── + if (ollamaGenerateCount.HasValue) + { + var ollamaHost = Environment.GetEnvironmentVariable("OLLAMA_HOST") ?? "http://localhost:11434"; + var dir = Path.GetDirectoryName(Path.GetFullPath(outputPath)); + var jsonlPath = dir is not null + ? Path.Combine(dir, "ollama_training_data.jsonl") + : "ollama_training_data.jsonl"; + + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + List generated = []; + using (var synthesizer = new AiTrainingDataSynthesizer(ollamaHost: ollamaHost)) + { + await AnsiConsole.Progress() + .StartAsync(async ctx => + { + var task = ctx.AddTask("[green]Synthesizing training examples[/]", + maxValue: ollamaGenerateCount.Value); + generated = await synthesizer.GenerateAsync( + ollamaGenerateCount.Value, + (done, _) => task.Value = done); + }); + } + + TrainingDataGenerator.WriteCsv(generated, jsonlPath); + AnsiConsole.MarkupLine($"[green]Synthesized {generated.Count} examples → {jsonlPath}[/]"); + return 0; + } + + // ── Ollama-based validation ────────────────────────────────────── + if (ollamaValidate) + { + if (dataPath is null) + { + AnsiConsole.MarkupLine("[red]--ollama-validate requires --data [/]"); + return 1; + } + + if (!File.Exists(dataPath)) + { + AnsiConsole.MarkupLine($"[red]Data file not found: {dataPath}[/]"); + return 1; + } + + var ollamaHost = Environment.GetEnvironmentVariable("OLLAMA_HOST") ?? "http://localhost:11434"; + var prompts = TrainingDataGenerator.ReadCsv(dataPath); + + List discrepancies = []; + using (var synthesizer = new AiTrainingDataSynthesizer(ollamaHost: ollamaHost)) + { + await AnsiConsole.Progress() + .StartAsync(async ctx => + { + var task = ctx.AddTask("[green]Validating against Ollama[/]", + maxValue: prompts.Count); + discrepancies = await synthesizer.ValidateAsync( + prompts, + (done, _) => task.Value = done); + }); + } + + await File.WriteAllTextAsync("validate_summary.txt", discrepancies.Count.ToString()); + AnsiConsole.MarkupLine($"[cyan]Validation complete — {discrepancies.Count}/{prompts.Count} disagreements[/]"); + return 0; + } + if (generateOnly) { dataPath ??= Path.Combine(Path.GetTempPath(), "intent_training_data.csv");