Введение
C# разработчики все чаще сталкиваются с необходимостью интеграции искусственного интеллекта в свои приложения, будь то чат-боты, анализ текста, генерация изображений или умные помощники. В то время как Python долгое время был доминирующим языком в сфере AI, экосистема .NET активно развивается, предлагая современные решения для работы с ИИ. Сегодня C# разработчики имеют два основных пути: использование облачных API (OpenAI, Azure AI) или развертывание локальных моделей с помощью библиотек вроде ML.NET и ONNX Runtime. Каждый подход имеет свои преимущества: облачные решения предлагают мощные предобученные модели и простоту интеграции, в то время как локальные модели обеспечивают конфиденциальность данных, отсутствие зависимости от интернета и предсказуемую стоимость.
Интеграция с OpenAI API
1.1. Работа с GPT-4 и ChatGPT
1.1.1. Настройка и базовое использование
// Установка: dotnet add package OpenAI using OpenAI; using OpenAI.Chat; using OpenAI.Embeddings; public class OpenAIService { private readonly OpenAIClient _client; private readonly ILogger _logger; private readonly ChatHistory _chatHistory; public OpenAIService(string apiKey, ILogger logger) { _client = new OpenAIClient(apiKey); _logger = logger; _chatHistory = new ChatHistory(); // Инициализация системного промпта _chatHistory.AddSystemMessage("Ты полезный ассистент, который помогает разработчикам на C#."); } // Базовый запрос к GPT-4 public async Task GetCompletionAsync(string prompt, string model = "gpt-4") { try { var options = new ChatCompletionOptions { Model = model, Temperature = 0.7f, MaxTokens = 2000, FrequencyPenalty = 0.5f, PresencePenalty = 0.5f }; _chatHistory.AddUserMessage(prompt); var response = await _client.ChatEndpoint.GetCompletionAsync( _chatHistory, options); var assistantMessage = response.FirstChoice.Message; _chatHistory.AddAssistantMessage(assistantMessage.Content); _logger.LogInformation($"Used tokens: {response.Usage.TotalTokens}"); return assistantMessage.Content; } catch (Exception ex) { _logger.LogError(ex, "OpenAI API error"); throw; } } // Потоковая передача ответов (streaming) public async IAsyncEnumerable StreamCompletionAsync(string prompt) { _chatHistory.AddUserMessage(prompt); var options = new ChatCompletionOptions { Model = "gpt-4", Temperature = 0.7f, MaxTokens = 2000, Stream = true // Включаем streaming }; var completion = _client.ChatEndpoint.StreamCompletionAsync( _chatHistory, options); await foreach (var result in completion) { var content = result.FirstChoice.Message.Content; if (!string.IsNullOrEmpty(content)) { yield return content; } } // Сохраняем финальное сообщение в историю var finalMessage = await GetFinalMessageAsync(completion); _chatHistory.AddAssistantMessage(finalMessage); } // Функции (functions) для структурированных ответов public async Task GetStructuredResponseAsync(string query) { var functions = new List { new Function { Name = "generate_code", Description = "Генерирует код на C#", Parameters = new JsonSchema { Type = JsonSchemaType.Object, Properties = new Dictionary { ["code"] = new JsonSchema { Type = JsonSchemaType.String, Description = "Сгенерированный код" }, ["language"] = new JsonSchema { Type = JsonSchemaType.String, Enum = new[] { "csharp", "python", "javascript" } }, ["complexity"] = new JsonSchema { Type = JsonSchemaType.String, Enum = new[] { "beginner", "intermediate", "advanced" } } }, Required = new[] { "code", "language" } } }, new Function { Name = "analyze_code", Description = "Анализирует код и предлагает улучшения", Parameters = new JsonSchema { Type = JsonSchemaType.Object, Properties = new Dictionary { ["analysis"] = new JsonSchema { Type = JsonSchemaType.String, Description = "Анализ кода" }, ["suggestions"] = new JsonSchema { Type = JsonSchemaType.Array, Items = new JsonSchema { Type = JsonSchemaType.String, Description = "Предложения по улучшению" } }, ["score"] = new JsonSchema { Type = JsonSchemaType.Integer, Minimum = 0, Maximum = 100 } } } } }; var options = new ChatCompletionOptions { Model = "gpt-4", Functions = functions, FunctionCall = "auto" }; _chatHistory.AddUserMessage(query); var response = await _client.ChatEndpoint.GetCompletionAsync( _chatHistory, options); var message = response.FirstChoice.Message; if (message.FunctionCall != null) { return await ProcessFunctionCallAsync(message.FunctionCall); } return new StructuredResponse { Text = message.Content }; } private async Task ProcessFunctionCallAsync(FunctionCall functionCall) { switch (functionCall.Name) { case "generate_code": var codeArgs = JsonSerializer.Deserialize (functionCall.Arguments); return await GenerateCodeAsync(codeArgs); case "analyze_code": var analysisArgs = JsonSerializer.Deserialize (functionCall.Arguments); return await AnalyzeCodeAsync(analysisArgs); default: throw new NotSupportedException($"Function {functionCall.Name} not supported"); } } // Работа с изображениями через DALL-E public async Task GenerateImageAsync(string prompt, ImageSize size = ImageSize.Large) { var request = new ImageGenerationRequest { Prompt = prompt, Size = size switch { ImageSize.Small => "256x256", ImageSize.Medium => "512x512", ImageSize.Large => "1024x1024", _ => "1024x1024" }, Quality = "standard", ResponseFormat = "url", Style = "vivid" }; var response = await _client.ImagesEndpoint.GenerateImageAsync(request); return new ImageResult { Url = response.First().Url, RevisedPrompt = response.First().RevisedPrompt, Created = response.First().Created }; } // Пакетная обработка запросов public async Task > ProcessBatchAsync(List prompts) { var tasks = prompts.Select(async prompt => { try { return await GetCompletionAsync(prompt); } catch (Exception ex) { _logger.LogError(ex, $"Error processing prompt: {prompt}"); return $"Error: {ex.Message}"; } }); var results = await Task.WhenAll(tasks); return results.ToList(); } }
1.2. Embeddings и семантический поиск
1.2.1. Создание и использование векторных представлений
public class EmbeddingService { private readonly OpenAIClient _client; private readonly Dictionary _embeddingsCache; private readonly ISemanticCache _semanticCache; public EmbeddingService(string apiKey) { _client = new OpenAIClient(apiKey); _embeddingsCache = new Dictionary (); _semanticCache = new MemorySemanticCache(); } // Генерация эмбеддингов для текста public async Task GetEmbeddingAsync(string text, string model = "text-embedding-3-small") { // Проверка кэша if (_embeddingsCache.TryGetValue(text, out var cachedEmbedding)) { return cachedEmbedding; } var request = new EmbeddingRequest { Input = text, Model = model, Dimensions = 1536 // Можно уменьшить для экономии }; var response = await _client.EmbeddingsEndpoint.CreateEmbeddingAsync(request); var embedding = response.First().Embedding.ToArray(); // Кэширование _embeddingsCache[text] = embedding; return embedding; } // Косинусное сходство public float CosineSimilarity(float[] vector1, float[] vector2) { if (vector1.Length != vector2.Length) throw new ArgumentException("Vectors must have the same length"); float dotProduct = 0; float magnitude1 = 0; float magnitude2 = 0; for (int i = 0; i > SemanticSearchAsync( string query, List documents, int topK = 5) { var queryEmbedding = await GetEmbeddingAsync(query); var results = new List (); foreach (var doc in documents) { var docEmbedding = await GetEmbeddingAsync(doc.Content); var similarity = CosineSimilarity(queryEmbedding, docEmbedding); results.Add(new SearchResult { Document = doc, Similarity = similarity }); } return results .OrderByDescending(r => r.Similarity) .Take(topK) .ToList(); } // Кластеризация документов public async Task > ClusterDocumentsAsync( List documents, int numberOfClusters = 5) { // Генерация эмбеддингов для всех документов var embeddings = new List (); foreach (var doc in documents) { var embedding = await GetEmbeddingAsync(doc.Content); embeddings.Add(embedding); } // K-means кластеризация var clusters = await KMeansClusteringAsync(embeddings, numberOfClusters); // Группировка документов по кластерам var documentClusters = new List (); for (int i = 0; i (); for (int j = 0; j clusters[i].Contains(idx))) }); } return documentClusters; } private async Task >> KMeansClusteringAsync( List embeddings, int k, int maxIterations = 100) { // Инициализация центроидов случайными точками var random = new Random(); var centroids = new List (); for (int i = 0; i >(); for (int iteration = 0; iteration new List ()) .ToList(); // Назначение точек ближайшим центроидам for (int i = 0; i new { Index = idx, Distance = EuclideanDistance(embeddings[i], c) }) .OrderBy(d => d.Distance) .First(); clusters[distances.Index].Add(i); } // Пересчет центроидов var newCentroids = new List (); for (int i = 0; i 0) { var clusterEmbeddings = clusters[i] .Select(idx => embeddings[idx]) .ToList(); newCentroids.Add(CalculateCentroid(clusterEmbeddings)); } else { // Если кластер пуст, инициализируем случайно newCentroids.Add(embeddings[random.Next(embeddings.Count)]); } } // Проверка сходимости if (CentroidsConverged(centroids, newCentroids, threshold: 0.001f)) { break; } centroids = newCentroids; } return clusters; } private float[] CalculateCentroid(IEnumerable vectors) { var first = vectors.First(); var centroid = new float[first.Length]; foreach (var vector in vectors) { for (int i = 0; i oldCentroids, List newCentroids, float threshold) { for (int i = 0; i threshold) { return false; } } return true; } }
Локальные модели ИИ на C#
2.1. Использование ONNX Runtime
2.1.1. Загрузка и выполнение ONNX моделей
// Установка: dotnet add package Microsoft.ML.OnnxRuntime // dotnet add package Microsoft.ML.OnnxRuntime.Managed using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; public class OnnxModelService : IDisposable { private readonly InferenceSession _session; private readonly ILogger _logger; private readonly Tokenizer _tokenizer; public OnnxModelService(string modelPath, ILogger logger) { var options = new SessionOptions { LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_WARNING, GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL, ExecutionMode = ExecutionMode.ORT_PARALLEL, InterOpNumThreads = Environment.ProcessorCount, IntraOpNumThreads = Environment.ProcessorCount }; // Использование CUDA если доступно if (HasCuda()) { options.AppendExecutionProvider_CUDA(); } else { options.AppendExecutionProvider_CPU(); } _session = new InferenceSession(modelPath, options); _logger = logger; _tokenizer = new Tokenizer(); LogModelInfo(); } private bool HasCuda() { try { var cudaSessionOptions = SessionOptions.MakeSessionOptionWithCudaProvider(); return true; } catch { return false; } } private void LogModelInfo() { _logger.LogInformation($"Model inputs: {_session.InputMetadata.Count}"); _logger.LogInformation($"Model outputs: {_session.OutputMetadata.Count}"); foreach (var input in _session.InputMetadata) { _logger.LogInformation($"Input: {input.Key}, Type: {input.Value.ElementType}, " + $"Shape: {string.Join(",", input.Value.Dimensions)}"); } } // Текстовая генерация с локальной моделью public async Task GenerateTextAsync(string prompt, int maxLength = 100) { var tokens = _tokenizer.Encode(prompt); var inputTensor = new DenseTensor ( new[] { 1, tokens.Length }, new[] { tokens.Select(t => (long)t).ToArray() }); var inputs = new List { NamedOnnxValue.CreateFromTensor("input_ids", inputTensor) }; var generatedText = prompt; for (int i = 0; i (); var nextToken = SampleToken(logits); if (nextToken == _tokenizer.EosTokenId) break; generatedText += _tokenizer.Decode(new[] { nextToken }); // Обновление inputs для следующей итерации tokens = tokens.Append(nextToken).ToArray(); inputTensor = new DenseTensor ( new[] { 1, tokens.Length }, new[] { tokens.Select(t => (long)t).ToArray() }); inputs[0] = NamedOnnxValue.CreateFromTensor("input_ids", inputTensor); } return generatedText; } private int SampleToken(Tensor logits) { // Реализация sampling с temperature var temperature = 0.7f; var probabilities = new float[logits.Length]; for (int i = 0; i ClassifyTextAsync(string text) { var tokens = _tokenizer.Encode(text); var inputIds = new DenseTensor ( new[] { 1, tokens.Length }, new[] { tokens.Select(t => (long)t).ToArray() }); var attentionMask = new DenseTensor ( new[] { 1, tokens.Length }, Enumerable.Repeat(1L, tokens.Length).ToArray()); var inputs = new List { NamedOnnxValue.CreateFromTensor("input_ids", inputIds), NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask) }; using var results = _session.Run(inputs); var logits = results.First().AsTensor (); var probabilities = Softmax(logits.ToArray()); var predictedClass = Array.IndexOf(probabilities, probabilities.Max()); return new ClassificationResult { ClassId = predictedClass, Confidence = probabilities[predictedClass], Probabilities = probabilities }; } private float[] Softmax(float[] logits) { var max = logits.Max(); var exp = logits.Select(x => (float)Math.Exp(x - max)).ToArray(); var sum = exp.Sum(); return exp.Select(x => x / sum).ToArray(); } // Пакетная обработка public async Task > ProcessBatchAsync(List texts) { var batchSize = 8; var results = new List (); for (int i = 0; i > ProcessSingleBatchAsync(List batch) { // Подготовка batch inputs var maxLength = batch.Max(t => _tokenizer.Encode(t).Length); var batchSize = batch.Count; var inputIds = new DenseTensor (new[] { batchSize, maxLength }); var attentionMask = new DenseTensor (new[] { batchSize, maxLength }); for (int i = 0; i { NamedOnnxValue.CreateFromTensor("input_ids", inputIds), NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask) }; using var results = _session.Run(inputs); var logits = results.First().AsTensor (); // Обработка результатов batch var batchResults = new List (); for (int i = 0; i
2.2. Локальные эмбеддинг-модели
2.2.1. Использование Sentence Transformers в C#
public class LocalEmbeddingService { private readonly OnnxModelService _modelService; private readonly Tokenizer _tokenizer; private readonly int _embeddingDimension; public LocalEmbeddingService(string modelPath) { _modelService = new OnnxModelService(modelPath, logger); _tokenizer = new Tokenizer(); // Определение размерности эмбеддингов из модели _embeddingDimension = GetEmbeddingDimension(modelPath); } public async Task GenerateEmbeddingAsync(string text) { var tokens = _tokenizer.Encode(text); var inputIds = new DenseTensor ( new[] { 1, tokens.Length }, new[] { tokens.Select(t => (long)t).ToArray() }); var attentionMask = new DenseTensor ( new[] { 1, tokens.Length }, Enumerable.Repeat(1L, tokens.Length).ToArray()); var inputs = new List { NamedOnnxValue.CreateFromTensor("input_ids", inputIds), NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask) }; using var results = _modelService.RunModel(inputs); var lastHiddenState = results.First().AsTensor (); // Mean pooling для получения sentence embedding return MeanPooling(lastHiddenState, attentionMask); } private float[] MeanPooling(Tensor lastHiddenState, Tensor attentionMask) { var batchSize = lastHiddenState.Dimensions[0]; var seqLength = lastHiddenState.Dimensions[1]; var hiddenSize = lastHiddenState.Dimensions[2]; var embeddings = new float[batchSize * hiddenSize]; for (int b = 0; b 0 ? sum / count : 0; } } return embeddings; } public async Task GenerateBatchEmbeddingsAsync(List texts) { var batchSize = texts.Count; var maxLength = texts.Max(t => _tokenizer.Encode(t).Length); var inputIds = new DenseTensor (new[] { batchSize, maxLength }); var attentionMask = new DenseTensor (new[] { batchSize, maxLength }); for (int i = 0; i { NamedOnnxValue.CreateFromTensor("input_ids", inputIds), NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask) }; using var results = _modelService.RunModel(inputs); var lastHiddenState = results.First().AsTensor (); var batchEmbeddings = new List (); for (int b = 0; b LocalSemanticSearchAsync( string query, List documents, int topK = 5) { var queryEmbedding = await GenerateEmbeddingAsync(query); // Генерация эмбеддингов для всех документов var documentEmbeddings = await GenerateBatchEmbeddingsAsync( documents.Select(d => d.Content).ToList()); var results = new List(); for (int i = 0; i r.Score) .Take(topK) .Select(r => new RankedDocument { Document = r.Doc, Score = r.Score }) .ToList(), QueryEmbedding = queryEmbedding }; } // Сохранение и загрузка эмбеддингов public void SaveEmbeddingsToFile(Dictionary embeddings, string filePath) { using var writer = new BinaryWriter(File.OpenWrite(filePath)); writer.Write(embeddings.Count); writer.Write(_embeddingDimension); foreach (var (text, embedding) in embeddings) { writer.Write(text); foreach (var value in embedding) { writer.Write(value); } } } public Dictionary LoadEmbeddingsFromFile(string filePath) { var embeddings = new Dictionary (); using var reader = new BinaryReader(File.OpenRead(filePath)); var count = reader.ReadInt32(); var dimension = reader.ReadInt32(); for (int i = 0; i existingEmbeddings, List newTexts) { var newEmbeddings = await GenerateBatchEmbeddingsAsync(newTexts); for (int i = 0; i
Гибридный подход: кэширование и оптимизация
3.1. Интеллектуальное кэширование ответов ИИ
3.1.1. Семантическое кэширование
public class SemanticCache { private readonly LocalEmbeddingService _embeddingService; private readonly Dictionary _cache; private readonly int _maxCacheSize; private readonly float _similarityThreshold; public SemanticCache( LocalEmbeddingService embeddingService, int maxCacheSize = 1000, float similarityThreshold = 0.85f) { _embeddingService = embeddingService; _cache = new Dictionary (); _maxCacheSize = maxCacheSize; _similarityThreshold = similarityThreshold; } public async Task GetOrAddAsync( string query, Func > valueFactory) { // Поиск семантически похожих запросов в кэше var similarEntry = await FindSimilarAsync(query); if (similarEntry != null) { return new CacheResult { Value = similarEntry.Value, Source = CacheSource.SemanticCache, Similarity = similarEntry.Similarity }; } // Если не найдено, вычисляем новое значение var value = await valueFactory(); var embedding = await _embeddingService.GenerateEmbeddingAsync(query); var entry = new CacheEntry { Key = query, Value = value, Embedding = embedding, CreatedAt = DateTime.UtcNow, AccessCount = 0 }; // Добавление в кэш с учетом размера AddToCache(query, entry); return new CacheResult { Value = value, Source = CacheSource.Fresh, Similarity = 1.0f }; } private async Task FindSimilarAsync(string query) { if (_cache.Count == 0) return null; var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(query); CacheEntry? bestMatch = null; float bestSimilarity = 0; foreach (var entry in _cache.Values) { var similarity = CosineSimilarity(queryEmbedding, entry.Embedding); if (similarity > _similarityThreshold && similarity > bestSimilarity) { bestSimilarity = similarity; bestMatch = entry; } } if (bestMatch != null) { // Обновление статистики использования bestMatch.AccessCount++; bestMatch.LastAccessed = DateTime.UtcNow; } return bestMatch; } private void AddToCache(string key, CacheEntry entry) { // Проверка размера кэша if (_cache.Count >= _maxCacheSize) { // Удаление наименее используемых записей var toRemove = _cache.OrderBy(e => e.Value.AccessCount) .ThenBy(e => e.Value.LastAccessed) .Take(_cache.Count - _maxCacheSize + 1) .ToList(); foreach (var item in toRemove) { _cache.Remove(item.Key); } } _cache[key] = entry; } public async Task PrewarmCacheAsync(List commonQueries, Func > valueFactory) { var tasks = commonQueries.Select(async query => { var value = await valueFactory(query); var embedding = await _embeddingService.GenerateEmbeddingAsync(query); return new CacheEntry { Key = query, Value = value, Embedding = embedding, CreatedAt = DateTime.UtcNow, AccessCount = 0 }; }); var entries = await Task.WhenAll(tasks); foreach (var entry in entries) { AddToCache(entry.Key, entry); } } public CacheStatistics GetStatistics() { return new CacheStatistics { TotalEntries = _cache.Count, HitRate = CalculateHitRate(), AverageSimilarity = CalculateAverageSimilarity(), MemoryUsage = CalculateMemoryUsage() }; } private float CalculateHitRate() { // Здесь должна быть логика отслеживания hits/misses return 0.85f; // Заглушка } }
3.2. Динамический выбор модели
public class AdaptiveAIService { private readonly OpenAIService _openAIService; private readonly LocalModelService _localModelService; private readonly ILogger _logger; private readonly AdaptiveRoutingStrategy _routingStrategy; public AdaptiveAIService( OpenAIService openAIService, LocalModelService localModelService, ILogger logger) { _openAIService = openAIService; _localModelService = localModelService; _logger = logger; _routingStrategy = new AdaptiveRoutingStrategy(); } public async Task ProcessAdaptiveAsync( string prompt, UserContext context, CancellationToken cancellationToken = default) { // Анализ запроса для выбора оптимальной стратегии var analysis = await AnalyzeRequestAsync(prompt, context); // Выбор модели на основе анализа var selectedModel = await SelectModelAsync(analysis, context); string result; switch (selectedModel.Type) { case ModelType.OpenAI_GPT4: result = await _openAIService.GetCompletionAsync( prompt, "gpt-4", cancellationToken); break; case ModelType.OpenAI_GPT35: result = await _openAIService.GetCompletionAsync( prompt, "gpt-3.5-turbo", cancellationToken); break; case ModelType.Local_Large: result = await _localModelService.GenerateWithLargeModelAsync( prompt, cancellationToken); break; case ModelType.Local_Fast: result = await _localModelService.GenerateWithFastModelAsync( prompt, cancellationToken); break; default: throw new InvalidOperationException("Unknown model type"); } // Логирование и обновление стратегии await LogAndUpdateStrategyAsync(analysis, selectedModel, result.Length); return result; } private async Task AnalyzeRequestAsync(string prompt, UserContext context) { var complexity = await EstimateComplexityAsync(prompt); var requiredQuality = EstimateRequiredQuality(context); var urgency = EstimateUrgency(context); var costSensitivity = context.CostSensitivity; return new RequestAnalysis { Complexity = complexity, RequiredQuality = requiredQuality, Urgency = urgency, CostSensitivity = costSensitivity, TokenEstimate = await EstimateTokensAsync(prompt), ContainsSensitiveData = await ContainsSensitiveDataAsync(prompt) }; } private async Task SelectModelAsync( RequestAnalysis analysis, UserContext context) { var candidates = new List (); // Кандидаты от OpenAI if (!analysis.ContainsSensitiveData && context.Budget > 0) { candidates.Add(new ModelCandidate { Type = ModelType.OpenAI_GPT4, EstimatedCost = analysis.TokenEstimate * 0.06f / 1000, // примерная стоимость EstimatedLatency = TimeSpan.FromSeconds(2), QualityScore = 0.95f }); candidates.Add(new ModelCandidate { Type = ModelType.OpenAI_GPT35, EstimatedCost = analysis.TokenEstimate * 0.002f / 1000, EstimatedLatency = TimeSpan.FromSeconds(1), QualityScore = 0.85f }); } // Локальные кандидаты if (analysis.Urgency == UrgencyLevel.Low || analysis.CostSensitivity == CostSensitivity.High) { candidates.Add(new ModelCandidate { Type = ModelType.Local_Large, EstimatedCost = 0, EstimatedLatency = TimeSpan.FromSeconds(5), QualityScore = 0.75f }); candidates.Add(new ModelCandidate { Type = ModelType.Local_Fast, EstimatedCost = 0, EstimatedLatency = TimeSpan.FromSeconds(1), QualityScore = 0.65f }); } // Выбор лучшего кандидата на основе взвешенной оценки var bestCandidate = candidates .OrderByDescending(c => CalculateScore(c, analysis, context)) .First(); return new ModelSelection { Type = bestCandidate.Type, Reason = GenerateSelectionReason(bestCandidate, analysis) }; } private float CalculateScore( ModelCandidate candidate, RequestAnalysis analysis, UserContext context) { var weights = new ScoringWeights { QualityWeight = analysis.RequiredQuality == QualityRequirement.High ? 0.5f : 0.3f, CostWeight = analysis.CostSensitivity == CostSensitivity.High ? 0.4f : 0.2f, LatencyWeight = analysis.Urgency == UrgencyLevel.High ? 0.3f : 0.1f }; // Нормализация оценок var qualityScore = candidate.QualityScore; var costScore = 1.0f - Math.Min(candidate.EstimatedCost / context.Budget, 1.0f); var latencyScore = 1.0f - Math.Min( (float)candidate.EstimatedLatency.TotalSeconds / 10, 1.0f); return qualityScore * weights.QualityWeight + costScore * weights.CostWeight + latencyScore * weights.LatencyWeight; } // Пакетная обработка с адаптивным роутингом public async Task > ProcessBatchAdaptiveAsync( List requests, CancellationToken cancellationToken = default) { var batches = new Dictionary >(); // Группировка запросов по выбранной модели foreach (var request in requests) { var analysis = await AnalyzeRequestAsync(request.Prompt, request.Context); var modelSelection = await SelectModelAsync(analysis, request.Context); if (!batches.ContainsKey(modelSelection.Type)) { batches[modelSelection.Type] = new List (); } batches[modelSelection.Type].Add(request with { SelectedModel = modelSelection }); } // Параллельная обработка каждой группы var tasks = batches.Select(async batch => { var results = await ProcessModelBatchAsync( batch.Key, batch.Value, cancellationToken); return results; }); var allResults = await Task.WhenAll(tasks); return allResults.SelectMany(r => r).ToList(); } private async Task > ProcessModelBatchAsync( ModelType modelType, List requests, CancellationToken cancellationToken) { switch (modelType) { case ModelType.OpenAI_GPT4: case ModelType.OpenAI_GPT35: var prompts = requests.Select(r => r.Prompt).ToList(); var openAiResults = await _openAIService.ProcessBatchAsync(prompts); return requests.Zip(openAiResults, (req, res) => new ProcessResult { RequestId = req.Id, ModelUsed = modelType, Result = res, Cost = CalculateOpenAICost(res, modelType), Latency = TimeSpan.Zero // Должно измеряться }).ToList(); case ModelType.Local_Large: case ModelType.Local_Fast: var localResults = await _localModelService.ProcessBatchAsync( requests.Select(r => r.Prompt).ToList(), modelType == ModelType.Local_Large ? LocalModelSize.Large : LocalModelSize.Fast); return requests.Zip(localResults, (req, res) => new ProcessResult { RequestId = req.Id, ModelUsed = modelType, Result = res, Cost = 0, Latency = TimeSpan.Zero // Должно измеряться }).ToList(); default: throw new InvalidOperationException($"Unsupported model type: {modelType}"); } } }
Безопасность и мониторинг
4.1. Защита данных и конфиденциальность
public class SecureAIService { private readonly IDataProtector _dataProtector; private readonly IEncryptionService _encryptionService; private readonly IAnonymizationService _anonymizationService; private readonly IAuditLogger _auditLogger; public SecureAIService( IDataProtectionProvider dataProtectionProvider, IEncryptionService encryptionService, IAnonymizationService anonymizationService, IAuditLogger auditLogger) { _dataProtector = dataProtectionProvider.CreateProtector("AI.Data"); _encryptionService = encryptionService; _anonymizationService = anonymizationService; _auditLogger = auditLogger; } public async Task ProcessSecureAsync(string input, UserContext context) { // 1. Аудит входа await _auditLogger.LogInputAsync(input, context); // 2. Проверка на PII (Personally Identifiable Information) var piiDetection = await DetectPIIAsync(input); if (piiDetection.HasPII) { // 3. Анонимизация чувствительных данных input = await _anonymizationService.AnonymizeAsync(input, piiDetection.Entities); await _auditLogger.LogPIIEventAsync(piiDetection, context); } // 4. Проверка на вредоносный контент var safetyCheck = await CheckContentSafetyAsync(input); if (!safetyCheck.IsSafe) { throw new SecurityException($"Unsafe content detected: {safetyCheck.Reason}"); } // 5. Шифрование перед отправкой в облако (если нужно) string processedResult; if (context.UseCloudAI && !context.AllowUnencrypted) { var encryptedInput = await _encryptionService.EncryptAsync(input); var encryptedResult = await CallExternalAIAsync(encryptedInput); processedResult = await _encryptionService.DecryptAsync(encryptedResult); } else { processedResult = await CallLocalAIAsync(input); } // 6. Проверка выходных данных var outputSafetyCheck = await CheckContentSafetyAsync(processedResult); if (!outputSafetyCheck.IsSafe) { processedResult = "[CONTENT BLOCKED]"; await _auditLogger.LogBlockedOutputAsync(outputSafetyCheck, context); } // 7. Аудит выхода await _auditLogger.LogOutputAsync(processedResult, context); return processedResult; } private async Task DetectPIIAsync(string text) { // Использование локальной модели для обнаружения PII var entities = new List (); // Пример: обнаружение email, телефонов, имен var emailPattern = @"[\w\.-]+@[\w\.-]+\.\w+"; var phonePattern = @"\+?[\d\s\-\(\)]{10,}"; var emailMatches = Regex.Matches(text, emailPattern); var phoneMatches = Regex.Matches(text, phonePattern); foreach (Match match in emailMatches) { entities.Add(new PIEntity { Type = PIEntityType.Email, Value = match.Value, StartIndex = match.Index, EndIndex = match.Index + match.Length }); } foreach (Match match in phoneMatches) { entities.Add(new PIEntity { Type = PIEntityType.Phone, Value = match.Value, StartIndex = match.Index, EndIndex = match.Index + match.Length }); } return new PIIDetectionResult { HasPII = entities.Count > 0, Entities = entities }; } private async Task CheckContentSafetyAsync(string text) { // Использование локальной модели классификации контента var categories = new[] { "hate", "hate/threatening", "self-harm", "sexual", "sexual/minors", "violence", "violence/graphic" }; var scores = await ClassifyContentAsync(text, categories); var maxScore = scores.Max(); var maxIndex = Array.IndexOf(scores, maxScore); return new SafetyCheckResult { IsSafe = maxScore = 0.5 ? categories[maxIndex] : null, Scores = scores }; } // Регулярная проверка моделей на смещение (bias) public async Task AuditModelForBiasAsync( string modelId, List testSets) { var results = new List (); foreach (var testSet in testSets) { var groupResults = new Dictionary >(); foreach (var example in testSet.Examples) { var prediction = await GetModelPredictionAsync(modelId, example.Input); var score = EvaluatePrediction(prediction, example.ExpectedOutput); if (!groupResults.ContainsKey(example.DemographicGroup)) { groupResults[example.DemographicGroup] = new List (); } groupResults[example.DemographicGroup].Add(score); } // Статистический анализ различий между группами var biasMetrics = CalculateBiasMetrics(groupResults); results.Add(new BiasTestResult { TestSetName = testSet.Name, Metrics = biasMetrics, HasSignificantBias = biasMetrics.PValue r.Metrics.EffectSize) }; } }
4.2. Мониторинг и метрики
public class AIMonitoringService { private readonly IMetricsCollector _metricsCollector; private readonly IAlertService _alertService; private readonly ILogger _logger; private readonly Dictionary _modelMetrics; public AIMonitoringService( IMetricsCollector metricsCollector, IAlertService alertService, ILogger logger) { _metricsCollector = metricsCollector; _alertService = alertService; _logger = logger; _modelMetrics = new Dictionary (); } public async Task TrackRequestAsync( AIRequest request, AIResponse response, TimeSpan processingTime) { var modelKey = $"{request.ModelType}:{request.ModelName}"; if (!_modelMetrics.ContainsKey(modelKey)) { _modelMetrics[modelKey] = new ModelMetrics(); } var metrics = _modelMetrics[modelKey]; lock (metrics) { metrics.TotalRequests++; metrics.TotalProcessingTime += processingTime; metrics.AverageProcessingTime = metrics.TotalProcessingTime / metrics.TotalRequests; if (response.IsSuccess) { metrics.SuccessfulRequests++; } else { metrics.FailedRequests++; metrics.LastError = response.Error; metrics.LastErrorTime = DateTime.UtcNow; } // Токены и стоимость metrics.TotalInputTokens += request.InputTokens; metrics.TotalOutputTokens += response.OutputTokens; metrics.TotalCost += CalculateCost(request, response); // Качество (если есть ground truth) if (request.ExpectedOutput != null) { var qualityScore = CalculateQualityScore(response.Output, request.ExpectedOutput); metrics.QualityScores.Add(qualityScore); metrics.AverageQuality = metrics.QualityScores.Average(); } } // Публикация метрик await PublishMetricsAsync(modelKey, metrics); // Проверка алертов await CheckAlertsAsync(modelKey, metrics); } private async Task CheckAlertsAsync(string modelKey, ModelMetrics metrics) { var alerts = new List (); // Алерт на высокую частоту ошибок var errorRate = (double)metrics.FailedRequests / metrics.TotalRequests; if (errorRate > 0.05) // 5% ошибок { alerts.Add(new Alert { Type = AlertType.HighErrorRate, Model = modelKey, Value = errorRate, Threshold = 0.05, Message = $"High error rate detected: {errorRate:P2}" }); } // Алерт на увеличение времени обработки if (metrics.AverageProcessingTime > TimeSpan.FromSeconds(10)) { alerts.Add(new Alert { Type = AlertType.HighLatency, Model = modelKey, Value = metrics.AverageProcessingTime.TotalSeconds, Threshold = 10, Message = $"High latency detected: {metrics.AverageProcessingTime.TotalSeconds:F1}s" }); } // Алерт на увеличение стоимости var avgCostPerRequest = metrics.TotalCost / metrics.TotalRequests; if (avgCostPerRequest > 0.1m) // $0.10 per request { alerts.Add(new Alert { Type = AlertType.HighCost, Model = modelKey, Value = (double)avgCostPerRequest, Threshold = 0.1, Message = $"High cost per request: ${avgCostPerRequest:F4}" }); } // Алерт на снижение качества if (metrics.QualityScores.Count >= 100) { var recentQuality = metrics.QualityScores.TakeLast(100).Average(); var historicalQuality = metrics.QualityScores.SkipLast(100).Average(); if (recentQuality GetDashboardAsync() { var dashboard = new AIDashboard { Timestamp = DateTime.UtcNow, Models = _modelMetrics.Select(kvp => new ModelDashboard { ModelKey = kvp.Key, Metrics = kvp.Value, HealthStatus = CalculateHealthStatus(kvp.Value) }).ToList(), Summary = new SummaryMetrics { TotalRequests = _modelMetrics.Sum(m => m.Value.TotalRequests), TotalCost = _modelMetrics.Sum(m => m.Value.TotalCost), AverageLatency = TimeSpan.FromSeconds( _modelMetrics.Average(m => m.Value.AverageProcessingTime.TotalSeconds)), OverallHealth = CalculateOverallHealth() } }; // Добавление трендов dashboard.Trends = await CalculateTrendsAsync(); return dashboard; } private async Task > CalculateTrendsAsync() { var trends = new List (); var now = DateTime.UtcNow; // Запрос исторических данных (например, из базы данных) var historicalData = await _metricsCollector.GetHistoricalMetricsAsync( now.AddHours(-24), now); // Расчет трендов для каждой модели foreach (var modelData in historicalData.GroupBy(d => d.ModelKey)) { var requestTrend = CalculateLinearTrend( modelData.Select(d => (double)d.RequestCount).ToArray()); var latencyTrend = CalculateLinearTrend( modelData.Select(d => d.AverageLatency.TotalSeconds).ToArray()); var costTrend = CalculateLinearTrend( modelData.Select(d => (double)d.TotalCost).ToArray()); trends.Add(new MetricTrend { ModelKey = modelData.Key, RequestTrend = requestTrend, LatencyTrend = latencyTrend, CostTrend = costTrend, IsIncreasing = requestTrend.Slope > 0 || costTrend.Slope > 0 }); } return trends; } private LinearTrend CalculateLinearTrend(double[] values) { if (values.Length (double)i).ToArray(); var n = values.Length; var sumX = x.Sum(); var sumY = values.Sum(); var sumXY = x.Zip(values, (a, b) => a * b).Sum(); var sumX2 = x.Sum(a => a * a); var sumY2 = values.Sum(a => a * a); var slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); var intercept = (sumY - slope * sumX) / n; var r2 = Math.Pow( (n * sumXY - sumX * sumY) / Math.Sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY)), 2); return new LinearTrend { Slope = slope, Intercept = intercept, R2 = r2 }; } }
Заключение
Интеграция искусственного интеллекта в C# приложения больше не является экзотической задачей, а становится стандартной практикой для современных разработчиков. Рассмотренные подходы — от облачных API до локальных моделей — предоставляют гибкие инструменты для решения различных бизнес-задач.