Искусственный интеллект на C#: интеграция с OpenAI и локальные модели

Введение

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<OpenAIService> _logger;
    private readonly ChatHistory _chatHistory;
    
    public OpenAIService(string apiKey, ILogger<OpenAIService> logger)
    {
        _client = new OpenAIClient(apiKey);
        _logger = logger;
        _chatHistory = new ChatHistory();
        
        // Инициализация системного промпта
        _chatHistory.AddSystemMessage("Ты полезный ассистент, который помогает разработчикам на C#.");
    }
    
    // Базовый запрос к GPT-4
    public async Task<string> 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<string> 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<StructuredResponse> GetStructuredResponseAsync(string query)
    {
        var functions = new List<Function>
        {
            new Function
            {
                Name = "generate_code",
                Description = "Генерирует код на C#",
                Parameters = new JsonSchema
                {
                    Type = JsonSchemaType.Object,
                    Properties = new Dictionary<string, JsonSchema>
                    {
                        ["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<string, JsonSchema>
                    {
                        ["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<StructuredResponse> ProcessFunctionCallAsync(FunctionCall functionCall)
    {
        switch (functionCall.Name)
        {
            case "generate_code":
                var codeArgs = JsonSerializer.Deserialize<CodeGenerationArgs>(functionCall.Arguments);
                return await GenerateCodeAsync(codeArgs);
                
            case "analyze_code":
                var analysisArgs = JsonSerializer.Deserialize<CodeAnalysisArgs>(functionCall.Arguments);
                return await AnalyzeCodeAsync(analysisArgs);
                
            default:
                throw new NotSupportedException($"Function {functionCall.Name} not supported");
        }
    }
    
    // Работа с изображениями через DALL-E
    public async Task<ImageResult> 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<List<string>> ProcessBatchAsync(List<string> 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<string, float[]> _embeddingsCache;
    private readonly ISemanticCache _semanticCache;
    
    public EmbeddingService(string apiKey)
    {
        _client = new OpenAIClient(apiKey);
        _embeddingsCache = new Dictionary<string, float[]>();
        _semanticCache = new MemorySemanticCache();
    }
    
    // Генерация эмбеддингов для текста
    public async Task<float[]> 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 < vector1.Length; i++)
        {
            dotProduct += vector1[i] * vector2[i];
            magnitude1 += vector1[i] * vector1[i];
            magnitude2 += vector2[i] * vector2[i];
        }
        
        magnitude1 = (float)Math.Sqrt(magnitude1);
        magnitude2 = (float)Math.Sqrt(magnitude2);
        
        if (magnitude1 == 0 || magnitude2 == 0)
            return 0;
        
        return dotProduct / (magnitude1 * magnitude2);
    }
    
    // Семантический поиск
    public async Task<List<SearchResult>> SemanticSearchAsync(
        string query, 
        List<Document> documents, 
        int topK = 5)
    {
        var queryEmbedding = await GetEmbeddingAsync(query);
        var results = new List<SearchResult>();
        
        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<List<DocumentCluster>> ClusterDocumentsAsync(
        List<Document> documents, 
        int numberOfClusters = 5)
    {
        // Генерация эмбеддингов для всех документов
        var embeddings = new List<float[]>();
        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<DocumentCluster>();
        
        for (int i = 0; i < clusters.Count; i++)
        {
            var clusterDocs = new List<Document>();
            for (int j = 0; j < clusters[i].Count; j++)
            {
                clusterDocs.Add(documents[clusters[i][j]]);
            }
            
            documentClusters.Add(new DocumentCluster
            {
                Id = i,
                Documents = clusterDocs,
                Center = CalculateCentroid(embeddings.Where((_, idx) => clusters[i].Contains(idx)))
            });
        }
        
        return documentClusters;
    }
    
    private async Task<List<List<int>>> KMeansClusteringAsync(
        List<float[]> embeddings, 
        int k, 
        int maxIterations = 100)
    {
        // Инициализация центроидов случайными точками
        var random = new Random();
        var centroids = new List<float[]>();
        
        for (int i = 0; i < k; i++)
        {
            centroids.Add(embeddings[random.Next(embeddings.Count)]);
        }
        
        var clusters = new List<List<int>>();
        
        for (int iteration = 0; iteration < maxIterations; iteration++)
        {
            // Сброс кластеров
            clusters = Enumerable.Range(0, k)
                .Select(_ => new List<int>())
                .ToList();
            
            // Назначение точек ближайшим центроидам
            for (int i = 0; i < embeddings.Count; i++)
            {
                var distances = centroids
                    .Select((c, idx) => new 
                    { 
                        Index = idx, 
                        Distance = EuclideanDistance(embeddings[i], c) 
                    })
                    .OrderBy(d => d.Distance)
                    .First();
                
                clusters[distances.Index].Add(i);
            }
            
            // Пересчет центроидов
            var newCentroids = new List<float[]>();
            
            for (int i = 0; i < k; i++)
            {
                if (clusters[i].Count > 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<float[]> vectors)
    {
        var first = vectors.First();
        var centroid = new float[first.Length];
        
        foreach (var vector in vectors)
        {
            for (int i = 0; i < vector.Length; i++)
            {
                centroid[i] += vector[i];
            }
        }
        
        int count = vectors.Count();
        for (int i = 0; i < centroid.Length; i++)
        {
            centroid[i] /= count;
        }
        
        return centroid;
    }
    
    private float EuclideanDistance(float[] a, float[] b)
    {
        float sum = 0;
        for (int i = 0; i < a.Length; i++)
        {
            float diff = a[i] - b[i];
            sum += diff * diff;
        }
        return (float)Math.Sqrt(sum);
    }
    
    private bool CentroidsConverged(
        List<float[]> oldCentroids, 
        List<float[]> newCentroids, 
        float threshold)
    {
        for (int i = 0; i < oldCentroids.Count; i++)
        {
            if (EuclideanDistance(oldCentroids[i], newCentroids[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<OnnxModelService> _logger;
    private readonly Tokenizer _tokenizer;
    
    public OnnxModelService(string modelPath, ILogger<OnnxModelService> 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<string> GenerateTextAsync(string prompt, int maxLength = 100)
    {
        var tokens = _tokenizer.Encode(prompt);
        
        var inputTensor = new DenseTensor<long>(
            new[] { 1, tokens.Length }, 
            new[] { tokens.Select(t => (long)t).ToArray() });
        
        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input_ids", inputTensor)
        };
        
        var generatedText = prompt;
        
        for (int i = 0; i < maxLength; i++)
        {
            using var results = _session.Run(inputs);
            
            var logits = results.First().AsTensor<float>();
            var nextToken = SampleToken(logits);
            
            if (nextToken == _tokenizer.EosTokenId)
                break;
            
            generatedText += _tokenizer.Decode(new[] { nextToken });
            
            // Обновление inputs для следующей итерации
            tokens = tokens.Append(nextToken).ToArray();
            inputTensor = new DenseTensor<long>(
                new[] { 1, tokens.Length }, 
                new[] { tokens.Select(t => (long)t).ToArray() });
            
            inputs[0] = NamedOnnxValue.CreateFromTensor("input_ids", inputTensor);
        }
        
        return generatedText;
    }
    
    private int SampleToken(Tensor<float> logits)
    {
        // Реализация sampling с temperature
        var temperature = 0.7f;
        var probabilities = new float[logits.Length];
        
        for (int i = 0; i < logits.Length; i++)
        {
            probabilities[i] = (float)Math.Exp(logits[i] / temperature);
        }
        
        // Softmax
        var sum = probabilities.Sum();
        for (int i = 0; i < probabilities.Length; i++)
        {
            probabilities[i] /= sum;
        }
        
        // Выбор токена на основе вероятностей
        var random = new Random();
        var randomValue = random.NextDouble();
        var cumulative = 0.0;
        
        for (int i = 0; i < probabilities.Length; i++)
        {
            cumulative += probabilities[i];
            if (randomValue <= cumulative)
            {
                return i;
            }
        }
        
        return probabilities.Length - 1;
    }
    
    // Классификация текста
    public async Task<ClassificationResult> ClassifyTextAsync(string text)
    {
        var tokens = _tokenizer.Encode(text);
        
        var inputIds = new DenseTensor<long>(
            new[] { 1, tokens.Length }, 
            new[] { tokens.Select(t => (long)t).ToArray() });
        
        var attentionMask = new DenseTensor<long>(
            new[] { 1, tokens.Length },
            Enumerable.Repeat(1L, tokens.Length).ToArray());
        
        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input_ids", inputIds),
            NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask)
        };
        
        using var results = _session.Run(inputs);
        var logits = results.First().AsTensor<float>();
        
        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<List<string>> ProcessBatchAsync(List<string> texts)
    {
        var batchSize = 8;
        var results = new List<string>();
        
        for (int i = 0; i < texts.Count; i += batchSize)
        {
            var batch = texts.Skip(i).Take(batchSize).ToList();
            var batchResults = await ProcessSingleBatchAsync(batch);
            results.AddRange(batchResults);
            
            _logger.LogInformation($"Processed batch {i / batchSize + 1}/" +
                                 $"{Math.Ceiling((double)texts.Count / batchSize)}");
        }
        
        return results;
    }
    
    private async Task<List<string>> ProcessSingleBatchAsync(List<string> batch)
    {
        // Подготовка batch inputs
        var maxLength = batch.Max(t => _tokenizer.Encode(t).Length);
        var batchSize = batch.Count;
        
        var inputIds = new DenseTensor<long>(new[] { batchSize, maxLength });
        var attentionMask = new DenseTensor<long>(new[] { batchSize, maxLength });
        
        for (int i = 0; i < batch.Count; i++)
        {
            var tokens = _tokenizer.Encode(batch[i]);
            
            for (int j = 0; j < tokens.Length; j++)
            {
                inputIds[i, j] = tokens[j];
                attentionMask[i, j] = 1;
            }
            
            // Padding
            for (int j = tokens.Length; j < maxLength; j++)
            {
                inputIds[i, j] = _tokenizer.PadTokenId;
                attentionMask[i, j] = 0;
            }
        }
        
        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input_ids", inputIds),
            NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask)
        };
        
        using var results = _session.Run(inputs);
        var logits = results.First().AsTensor<float>();
        
        // Обработка результатов batch
        var batchResults = new List<string>();
        
        for (int i = 0; i < batchSize; i++)
        {
            var slice = logits.GetSubTensor(i);
            var generated = ProcessSingleResult(slice);
            batchResults.Add(generated);
        }
        
        return batchResults;
    }
    
    public void Dispose()
    {
        _session?.Dispose();
    }
}

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<float[]> GenerateEmbeddingAsync(string text)
    {
        var tokens = _tokenizer.Encode(text);
        
        var inputIds = new DenseTensor<long>(
            new[] { 1, tokens.Length }, 
            new[] { tokens.Select(t => (long)t).ToArray() });
        
        var attentionMask = new DenseTensor<long>(
            new[] { 1, tokens.Length },
            Enumerable.Repeat(1L, tokens.Length).ToArray());
        
        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input_ids", inputIds),
            NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask)
        };
        
        using var results = _modelService.RunModel(inputs);
        var lastHiddenState = results.First().AsTensor<float>();
        
        // Mean pooling для получения sentence embedding
        return MeanPooling(lastHiddenState, attentionMask);
    }
    
    private float[] MeanPooling(Tensor<float> lastHiddenState, Tensor<long> 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 < batchSize; b++)
        {
            for (int h = 0; h < hiddenSize; h++)
            {
                float sum = 0;
                int count = 0;
                
                for (int s = 0; s < seqLength; s++)
                {
                    if (attentionMask[b, s] == 1)
                    {
                        sum += lastHiddenState[b, s, h];
                        count++;
                    }
                }
                
                embeddings[b * hiddenSize + h] = count > 0 ? sum / count : 0;
            }
        }
        
        return embeddings;
    }
    
    public async Task<float[][]> GenerateBatchEmbeddingsAsync(List<string> texts)
    {
        var batchSize = texts.Count;
        var maxLength = texts.Max(t => _tokenizer.Encode(t).Length);
        
        var inputIds = new DenseTensor<long>(new[] { batchSize, maxLength });
        var attentionMask = new DenseTensor<long>(new[] { batchSize, maxLength });
        
        for (int i = 0; i < texts.Count; i++)
        {
            var tokens = _tokenizer.Encode(texts[i]);
            
            for (int j = 0; j < tokens.Length; j++)
            {
                inputIds[i, j] = tokens[j];
                attentionMask[i, j] = 1;
            }
            
            for (int j = tokens.Length; j < maxLength; j++)
            {
                inputIds[i, j] = _tokenizer.PadTokenId;
                attentionMask[i, j] = 0;
            }
        }
        
        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input_ids", inputIds),
            NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask)
        };
        
        using var results = _modelService.RunModel(inputs);
        var lastHiddenState = results.First().AsTensor<float>();
        
        var batchEmbeddings = new List<float[]>();
        
        for (int b = 0; b < batchSize; b++)
        {
            var embedding = ExtractSingleEmbedding(lastHiddenState, attentionMask, b);
            batchEmbeddings.Add(embedding);
        }
        
        return batchEmbeddings.ToArray();
    }
    
    // Семантический поиск с локальными эмбеддингами
    public async Task<SearchResult> LocalSemanticSearchAsync(
        string query, 
        List<Document> documents, 
        int topK = 5)
    {
        var queryEmbedding = await GenerateEmbeddingAsync(query);
        
        // Генерация эмбеддингов для всех документов
        var documentEmbeddings = await GenerateBatchEmbeddingsAsync(
            documents.Select(d => d.Content).ToList());
        
        var results = new List<(Document Doc, float Score)>();
        
        for (int i = 0; i < documents.Count; i++)
        {
            var similarity = CosineSimilarity(
                queryEmbedding, 
                documentEmbeddings[i]);
            
            results.Add((documents[i], similarity));
        }
        
        return new SearchResult
        {
            TopDocuments = results
                .OrderByDescending(r => r.Score)
                .Take(topK)
                .Select(r => new RankedDocument
                {
                    Document = r.Doc,
                    Score = r.Score
                })
                .ToList(),
            QueryEmbedding = queryEmbedding
        };
    }
    
    // Сохранение и загрузка эмбеддингов
    public void SaveEmbeddingsToFile(Dictionary<string, float[]> 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<string, float[]> LoadEmbeddingsFromFile(string filePath)
    {
        var embeddings = new Dictionary<string, float[]>();
        
        using var reader = new BinaryReader(File.OpenRead(filePath));
        
        var count = reader.ReadInt32();
        var dimension = reader.ReadInt32();
        
        for (int i = 0; i < count; i++)
        {
            var text = reader.ReadString();
            var embedding = new float[dimension];
            
            for (int j = 0; j < dimension; j++)
            {
                embedding[j] = reader.ReadSingle();
            }
            
            embeddings[text] = embedding;
        }
        
        return embeddings;
    }
    
    // Инкрементальное обновление эмбеддингов
    public async Task UpdateEmbeddingsAsync(
        Dictionary<string, float[]> existingEmbeddings,
        List<string> newTexts)
    {
        var newEmbeddings = await GenerateBatchEmbeddingsAsync(newTexts);
        
        for (int i = 0; i < newTexts.Count; i++)
        {
            existingEmbeddings[newTexts[i]] = newEmbeddings[i];
        }
    }
}

Гибридный подход: кэширование и оптимизация

3.1. Интеллектуальное кэширование ответов ИИ

3.1.1. Семантическое кэширование

public class SemanticCache
{
    private readonly LocalEmbeddingService _embeddingService;
    private readonly Dictionary<string, CacheEntry> _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<string, CacheEntry>();
        _maxCacheSize = maxCacheSize;
        _similarityThreshold = similarityThreshold;
    }
    
    public async Task<CacheResult> GetOrAddAsync(
        string query, 
        Func<Task<string>> 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<CacheEntry?> 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<string> commonQueries, Func<string, Task<string>> 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<AdaptiveAIService> _logger;
    private readonly AdaptiveRoutingStrategy _routingStrategy;
    
    public AdaptiveAIService(
        OpenAIService openAIService,
        LocalModelService localModelService,
        ILogger<AdaptiveAIService> logger)
    {
        _openAIService = openAIService;
        _localModelService = localModelService;
        _logger = logger;
        _routingStrategy = new AdaptiveRoutingStrategy();
    }
    
    public async Task<string> 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<RequestAnalysis> 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<ModelSelection> SelectModelAsync(
        RequestAnalysis analysis, 
        UserContext context)
    {
        var candidates = new List<ModelCandidate>();
        
        // Кандидаты от 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<List<ProcessResult>> ProcessBatchAdaptiveAsync(
        List<ProcessRequest> requests,
        CancellationToken cancellationToken = default)
    {
        var batches = new Dictionary<ModelType, List<ProcessRequest>>();
        
        // Группировка запросов по выбранной модели
        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<ProcessRequest>();
            }
            
            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<List<ProcessResult>> ProcessModelBatchAsync(
        ModelType modelType,
        List<ProcessRequest> 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<string> 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<PIIDetectionResult> DetectPIIAsync(string text)
    {
        // Использование локальной модели для обнаружения PII
        var entities = new List<PIEntity>();
        
        // Пример: обнаружение 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<SafetyCheckResult> 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, // Порог безопасности
            Reason = maxScore >= 0.5 ? categories[maxIndex] : null,
            Scores = scores
        };
    }
    
    // Регулярная проверка моделей на смещение (bias)
    public async Task<BiasAuditResult> AuditModelForBiasAsync(
        string modelId, 
        List<BiasTestSet> testSets)
    {
        var results = new List<BiasTestResult>();
        
        foreach (var testSet in testSets)
        {
            var groupResults = new Dictionary<string, List<float>>();
            
            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<float>();
                }
                
                groupResults[example.DemographicGroup].Add(score);
            }
            
            // Статистический анализ различий между группами
            var biasMetrics = CalculateBiasMetrics(groupResults);
            
            results.Add(new BiasTestResult
            {
                TestSetName = testSet.Name,
                Metrics = biasMetrics,
                HasSignificantBias = biasMetrics.PValue < 0.05
            });
        }
        
        return new BiasAuditResult
        {
            ModelId = modelId,
            AuditDate = DateTime.UtcNow,
            TestResults = results,
            OverallBiasScore = results.Average(r => r.Metrics.EffectSize)
        };
    }
}

4.2. Мониторинг и метрики

public class AIMonitoringService
{
    private readonly IMetricsCollector _metricsCollector;
    private readonly IAlertService _alertService;
    private readonly ILogger<AIMonitoringService> _logger;
    private readonly Dictionary<string, ModelMetrics> _modelMetrics;
    
    public AIMonitoringService(
        IMetricsCollector metricsCollector,
        IAlertService alertService,
        ILogger<AIMonitoringService> logger)
    {
        _metricsCollector = metricsCollector;
        _alertService = alertService;
        _logger = logger;
        _modelMetrics = new Dictionary<string, ModelMetrics>();
    }
    
    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<Alert>();
        
        // Алерт на высокую частоту ошибок
        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 < historicalQuality * 0.9) // 10% снижение
            {
                alerts.Add(new Alert
                {
                    Type = AlertType.QualityDegradation,
                    Model = modelKey,
                    Value = recentQuality,
                    HistoricalValue = historicalQuality,
                    Message = $"Quality degradation detected: {recentQuality:F3} vs {historicalQuality:F3}"
                });
            }
        }
        
        // Отправка алертов
        foreach (var alert in alerts)
        {
            await _alertService.SendAlertAsync(alert);
            _logger.LogWarning($"Alert triggered: {alert.Message}");
        }
    }
    
    public async Task<AIDashboard> 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<List<MetricTrend>> CalculateTrendsAsync()
    {
        var trends = new List<MetricTrend>();
        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 < 2)
            return new LinearTrend { Slope = 0, R2 = 0 };
        
        var x = Enumerable.Range(0, values.Length).Select(i => (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 до локальных моделей — предоставляют гибкие инструменты для решения различных бизнес-задач.