using ManagerService.Data; using ManagerService.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace ManagerService.Tests.Infrastructure { /// /// Embeddings déterministes, sans appel réseau. Le texte pilote directement la /// direction du vecteur, ce qui permet d'écrire « ce morceau est proche de cette /// question » comme un fait vérifiable plutôt que comme une supposition. /// /// /// Convention : un texte préfixé axis:N produit le vecteur unitaire de l'axe N, /// éventuellement mêlé d'un peu de bruit stable (axis:N#seed). Deux textes sur /// des axes différents sont donc quasi orthogonaux — distance cosinus proche de 1. /// Ce qui suit les chiffres du seed est ignoré : cela permet d'écrire des textes /// distincts qui partagent un même vecteur, sans quoi la déduplication par texte de /// SearchAsync écraserait les lignes et fausserait tout comptage de résultats. /// public class FakeEmbeddingService : IEmbeddingService { public int Dimensions => ContentEmbedding.Dimensions; public Task EmbedAsync(string text, CancellationToken cancellationToken = default) => Task.FromResult(Embed(text)); public Task> EmbedBatchAsync(IReadOnlyList texts, CancellationToken cancellationToken = default) => Task.FromResult>(texts.Select(Embed).ToList()); public float[] Embed(string text) { var vector = new float[Dimensions]; var axis = 0; var seed = 0; if (text != null && text.StartsWith("axis:")) { var payload = text.Substring("axis:".Length); var hash = payload.IndexOf('#'); if (hash >= 0) { var digits = new string(payload.Substring(hash + 1).TakeWhile(char.IsDigit).ToArray()); int.TryParse(digits, out seed); payload = payload.Substring(0, hash); } int.TryParse(payload, out axis); } else { axis = Math.Abs((text ?? "").GetHashCode()) % Dimensions; } vector[axis % Dimensions] = 1f; // Bruit stable : deux morceaux du même axe ne sont pas strictement identiques, // sinon HNSW n'aurait aucun ordre à produire entre eux. if (seed != 0) vector[(axis + 1 + (seed % 16)) % Dimensions] = 0.01f * (seed % 10); return vector; } } }