Базовые примеры

Простые примеры для начала работы с SmartRAG.

Загрузка и поиск документов

[ApiController]
[Route("api/[controller]")]
public class DocumentController : ControllerBase
{
    private readonly IDocumentService _documentService;
    private readonly ILogger<DocumentController> _logger;

    public DocumentController(IDocumentService documentService, ILogger<DocumentController> logger)
    {
        _documentService = documentService;
        _logger = logger;
    }

    [HttpPost("upload")]
    public async Task<ActionResult<Document>> UploadDocument(IFormFile file)
    {
        try
        {
            if (file == null || file.Length == 0)
                return BadRequest("Файл не выбран");

            var document = await _documentService.UploadDocumentAsync(file);
            _logger.LogInformation("Документ {FileName} успешно загружен", file.FileName);
            
            return Ok(document);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Ошибка при загрузке документа");
            return StatusCode(500, "Внутренняя ошибка сервера");
        }
    }

    [HttpGet("search")]
    public async Task<ActionResult<IEnumerable<DocumentChunk>>> SearchDocuments(
        [FromQuery] string query, 
        [FromQuery] int maxResults = 10)
    {
        try
        {
            if (string.IsNullOrWhiteSpace(query))
                return BadRequest("Поисковый запрос не может быть пустым");

            var results = await _documentService.SearchDocumentsAsync(query, maxResults);
            _logger.LogInformation("Поиск по '{Query}' дал {Count} результатов", query, results.Count());
            
            return Ok(results);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Ошибка при поиске документов");
            return StatusCode(500, "Внутренняя ошибка сервера");
        }
    }
}

Генерация RAG ответа

[HttpPost("ask")]
public async Task<ActionResult<RagResponse>> AskQuestion([FromBody] AskQuestionRequest request)
{
    try
    {
        if (string.IsNullOrWhiteSpace(request.Question))
            return BadRequest("Вопрос не может быть пустым");

        var response = await _documentService.GenerateRagAnswerAsync(request.Question, request.MaxResults ?? 5);
        _logger.LogInformation("RAG ответ на вопрос '{Question}' сгенерирован", request.Question);
        
        return Ok(response);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Ошибка при генерации RAG ответа");
        return StatusCode(500, "Внутренняя ошибка сервера");
    }
}

public class AskQuestionRequest
{
    public string Question { get; set; }
    public int? MaxResults { get; set; }
}

Расширенные примеры

Более сложные случаи использования и расширенные функции.

Умное определение намерения запроса

Автоматически направляйте запросы в чат или поиск документов на основе анализа намерения:

public async Task<QueryResult> ProcessQueryAsync(string query)
{
    // Анализировать намерение запроса
    var intent = await _queryIntentService.AnalyzeIntentAsync(query);
    
    switch (intent.Type)
    {
        case QueryIntentType.Chat:
            // Направить к разговорному ИИ
            return await _chatService.ProcessChatQueryAsync(query);
            
        case QueryIntentType.DocumentSearch:
            // Направить к поиску документов
            var searchResults = await _documentService.SearchDocumentsAsync(query);
            return new QueryResult 
            { 
                Type = QueryResultType.DocumentSearch,
                Results = searchResults 
            };
            
        case QueryIntentType.Mixed:
            // Объединить оба подхода
            var chatResponse = await _chatService.ProcessChatQueryAsync(query);
            var docResults = await _documentService.SearchDocumentsAsync(query);
            
            return new QueryResult 
            { 
                Type = QueryResultType.Mixed,
                ChatResponse = chatResponse,
                DocumentResults = docResults 
            };
            
        default:
            throw new ArgumentException($"Неизвестный тип намерения: {intent.Type}");
    }
}

Расширенный семантический поиск

Расширенный поиск с гибридной оценкой (80% семантическая + 20% ключевые слова) и осведомленностью о контексте:

public async Task<IEnumerable<SearchResult>> EnhancedSearchAsync(
    string query, 
    SearchOptions options = null)
{
    // Настроить веса гибридной оценки
    var searchConfig = new EnhancedSearchConfiguration
    {
        SemanticWeight = 0.8,        // 80% семантическое сходство
        KeywordWeight = 0.2,          // 20% соответствие ключевым словам
        ContextWindowSize = 512,      // Окно осведомленности о контексте
        MinSimilarityThreshold = 0.6, // Минимальный порог сходства
        EnableFuzzyMatching = true,   // Нечеткое соответствие ключевым словам
        MaxResults = options?.MaxResults ?? 20
    };

    // Выполнить гибридный поиск
    var results = await _searchService.EnhancedSearchAsync(query, searchConfig);
    
    // Применить ранжирование с учетом контекста
    var rankedResults = await _rankingService.RankByContextAsync(results, query);
    
    return rankedResults;
}

Интеграция VoyageAI

Высококачественные эмбеддинги для моделей Anthropic Claude с использованием VoyageAI:

// Настроить интеграцию VoyageAI
services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-anthropic-api-key";
    
    // Включить VoyageAI для высококачественных эмбеддингов
    options.EnableVoyageAI = true;
    options.VoyageAI.ApiKey = "your-voyageai-api-key";
    options.VoyageAI.Model = "voyage-large-2"; // Последняя модель
    options.VoyageAI.Dimensions = 1536; // Размеры эмбеддингов
    options.VoyageAI.BatchSize = 100; // Пакетная обработка
});

// Использовать эмбеддинги VoyageAI в вашем сервисе
public async Task<IEnumerable<float[]>> GenerateEmbeddingsAsync(
    IEnumerable<string> texts)
{
    var embeddingService = serviceProvider.GetRequiredService<IVoyageAIEmbeddingService>();
    
    // Генерировать высококачественные эмбеддинги
    var embeddings = await embeddingService.GenerateEmbeddingsAsync(texts);
    
    return embeddings;
}

Расширенная конфигурация VoyageAI

// Расширенная конфигурация VoyageAI с пользовательскими настройками
var voyageAIConfig = new VoyageAIConfiguration
{
    ApiKey = "your-voyageai-api-key",
    Model = "voyage-large-2",
    Dimensions = 1536,
    BatchSize = 100,
    MaxRetries = 3,
    Timeout = TimeSpan.FromSeconds(30),
    EnableCompression = true,
    CustomHeaders = new Dictionary<string, string>
    {
        ["User-Agent"] = "SmartRAG/1.1.0"
    }
};

services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-anthropic-api-key";
    
    // Настроить VoyageAI с пользовательскими настройками
    options.EnableVoyageAI = true;
    options.VoyageAI = voyageAIConfig;
});

// Реализация контроллера
[HttpPost("generate-embeddings")]
public async Task<ActionResult<EmbeddingResponse>> GenerateEmbeddings(
    [FromBody] EmbeddingRequest request)
{
    try
    {
        var embeddingService = _serviceProvider.GetRequiredService<IVoyageAIEmbeddingService>();
        
        var embeddings = await embeddingService.GenerateEmbeddingsAsync(request.Texts);
        
        return Ok(new EmbeddingResponse
        {
            Embeddings = embeddings,
            Model = "voyage-large-2",
            Dimensions = embeddings.FirstOrDefault()?.Length ?? 0,
            TotalTokens = request.Texts.Sum(t => t.Split(' ').Length)
        });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Ошибка при генерации эмбеддингов с VoyageAI");
        return StatusCode(500, "Не удалось сгенерировать эмбеддинги");
    }
}

Конфигурация поиска

// Настроить расширенный семантический поиск
services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-api-key";
    
    // Включить расширенный семантический поиск
    options.EnableEnhancedSearch = true;
    options.SemanticWeight = 0.8;
    options.KeywordWeight = 0.2;
    options.ContextAwareness = true;
    options.FuzzyMatching = true;
});

// Использовать в вашем контроллере
[HttpGet("enhanced-search")]
public async Task<ActionResult<IEnumerable<SearchResult>>> EnhancedSearch(
    [FromQuery] string query,
    [FromQuery] int maxResults = 20)
{
    var options = new SearchOptions { MaxResults = maxResults };
    var results = await _searchService.EnhancedSearchAsync(query, options);
    return Ok(results);
}

Языково-агностический дизайн

SmartRAG работает с любым языком без жестко заданных языковых шаблонов или языково-специфичных правил:

// Языково-агностическая конфигурация - работает с любым языком
services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-api-key";
    
    // Включить языково-агностические функции
    options.LanguageAgnostic = true;
    options.AutoDetectLanguage = true;
    options.SupportedLanguages = new[] { "en", "tr", "de", "ru", "fr", "es", "ja", "ko", "zh" };
    
    // Нет жестко заданных языковых шаблонов
    options.EnableMultilingualSupport = true;
    options.FallbackLanguage = "en";
});

// Автоматически обрабатывать запросы на любом языке
public async Task<QueryResult> ProcessMultilingualQueryAsync(string query)
{
    // Язык определяется автоматически
    var detectedLanguage = await _languageService.DetectLanguageAsync(query);
    
    // Обрабатывать с помощью языково-агностических алгоритмов
    var result = await _queryProcessor.ProcessQueryAsync(query, new QueryOptions
    {
        Language = detectedLanguage,
        UseLanguageAgnosticProcessing = true
    });
    
    return result;
}

Расширенные языково-агностические функции

// Расширенная языково-агностическая конфигурация
var languageAgnosticConfig = new LanguageAgnosticConfiguration
{
    EnableLanguageDetection = true,
    EnableMultilingualEmbeddings = true,
    EnableCrossLanguageSearch = true,
    LanguageDetectionThreshold = 0.8,
    SupportedScripts = new[] { "Latin", "Cyrillic", "Arabic", "Chinese", "Japanese", "Korean" },
    EnableScriptNormalization = true,
    EnableUnicodeNormalization = true,
    FallbackStrategies = new[] { "transliteration", "romanization", "english" }
};

services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-api-key";
    
    // Настроить расширенные языково-агностические функции
    options.LanguageAgnostic = true;
    options.LanguageAgnosticConfig = languageAgnosticConfig;
});

// Контроллер для многоязычной обработки документов
[HttpPost("multilingual-upload")]
public async Task<ActionResult<MultilingualUploadResult>> UploadMultilingualDocument(
    [FromBody] MultilingualUploadRequest request)
{
    try
    {
        // Обработать документ на любом языке
        var document = await _documentService.UploadMultilingualDocumentAsync(
            request.Content, 
            request.FileName,
            request.DetectedLanguage);
        
        // Сгенерировать эмбеддинги с помощью языково-агностических алгоритмов
        var embeddings = await _embeddingService.GenerateMultilingualEmbeddingsAsync(
            document.Chunks,
            document.DetectedLanguage);
        
        // Сохранить с языковыми метаданными
        await _storageService.StoreMultilingualDocumentAsync(document, embeddings);
        
        return Ok(new MultilingualUploadResult
        {
            DocumentId = document.Id,
            DetectedLanguage = document.DetectedLanguage,
            LanguageConfidence = document.LanguageConfidence,
            TotalChunks = document.Chunks.Count,
            ProcessingTime = document.ProcessingTime
        });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Ошибка при многоязычной обработке документа");
        return StatusCode(500, "Не удалось обработать многоязычный документ");
    }
}

// Многоязычный поиск по всем языкам
[HttpGet("multilingual-search")]
public async Task<ActionResult<MultilingualSearchResult>> SearchMultilingual(
    [FromQuery] string query,
    [FromQuery] string[] languages = null,
    [FromQuery] int maxResults = 20)
{
    try
    {
        var searchOptions = new MultilingualSearchOptions
        {
            Query = query,
            TargetLanguages = languages ?? new[] { "auto" },
            MaxResults = maxResults,
            EnableCrossLanguageSearch = true,
            UseLanguageAgnosticScoring = true
        };
        
        var results = await _searchService.SearchMultilingualAsync(searchOptions);
        
        return Ok(new MultilingualSearchResult
        {
            Query = query,
            DetectedQueryLanguage = results.DetectedLanguage,
            Results = results.Results,
            CrossLanguageMatches = results.CrossLanguageMatches,
            TotalResults = results.TotalResults
        });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Ошибка при многоязычном поиске");
        return StatusCode(500, "Не удалось выполнить многоязычный поиск");
    }
}

Механизм повторных попыток Anthropic API

Расширенная логика повторных попыток для обработки ошибок HTTP 529 (Overloaded):

public async Task<ChatResponse> ProcessWithRetryAsync(string prompt, int maxRetries = 3)
{
    var retryPolicy = new ExponentialBackoffRetryPolicy
    {
        MaxRetries = maxRetries,
        BaseDelay = TimeSpan.FromSeconds(2),
        MaxDelay = TimeSpan.FromSeconds(30),
        JitterFactor = 0.1
    };

    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            var response = await _anthropicService.ChatAsync(new ChatRequest
            {
                Model = "claude-3-sonnet-20240229",
                MaxTokens = 1000,
                Messages = new[] { new Message { Role = "user", Content = prompt } }
            });

            return response;
        }
        catch (AnthropicApiException ex) when (ex.StatusCode == 529)
        {
            _logger.LogWarning("Anthropic API перегружен (HTTP 529), попытка {Attempt}/{MaxRetries}", 
                attempt, maxRetries);

            if (attempt == maxRetries)
            {
                throw new AnthropicServiceUnavailableException(
                    "Anthropic API в настоящее время перегружен после нескольких попыток", ex);
            }

            var delay = retryPolicy.CalculateDelay(attempt);
            await Task.Delay(delay);
        }
        catch (AnthropicApiException ex) when (ex.StatusCode == 429)
        {
            // Ограничение скорости - используйте экспоненциальную задержку
            var delay = retryPolicy.CalculateDelay(attempt);
            await Task.Delay(delay);
        }
    }

    throw new InvalidOperationException("Неожиданный выход из цикла повторных попыток");
}

Расширенная конфигурация повторных попыток

Настройте сложные политики повторных попыток для различных сценариев ошибок:

// Настройка расширенных политик повторных попыток
services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-anthropic-api-key";
    
    // Включить расширенный механизм повторных попыток
    options.EnableAdvancedRetry = true;
    options.RetryConfiguration = new AnthropicRetryConfiguration
    {
        MaxRetries = 5,
        BaseDelay = TimeSpan.FromSeconds(1),
        MaxDelay = TimeSpan.FromSeconds(60),
        JitterFactor = 0.15,
        EnableCircuitBreaker = true,
        CircuitBreakerThreshold = 10,
        CircuitBreakerTimeout = TimeSpan.FromMinutes(5),
        
        // Специальная обработка ошибок
        RetryOnStatusCodes = new[] { 429, 529, 500, 502, 503, 504 },
        ExponentialBackoff = true,
        LinearBackoff = false,
        
        // Пользовательские условия повторных попыток
        CustomRetryPredicate = async (exception, attempt) =>
        {
            if (exception is AnthropicApiException apiEx)
            {
                // Всегда повторять при 529 (Overloaded)
                if (apiEx.StatusCode == 529) return true;
                
                // Повторять при 429 (Rate Limited) с задержкой
                if (apiEx.StatusCode == 429) return attempt <= 3;
                
                // Повторять при ошибках сервера
                if (apiEx.StatusCode >= 500) return attempt <= 2;
            }
            
            return false;
        }
    };
});

// Используйте в вашем сервисе с обработкой повторных попыток
public class AnthropicService
{
    private readonly IAnthropicClient _client;
    private readonly IRetryPolicy _retryPolicy;
    private readonly ILogger _logger;

    public AnthropicService(IAnthropicClient client, IRetryPolicy retryPolicy, ILogger logger)
    {
        _client = client;
        _retryPolicy = retryPolicy;
        _logger = logger;
    }

    public async Task<ChatResponse> ChatWithRetryAsync(ChatRequest request)
    {
        return await _retryPolicy.ExecuteAsync(async () =>
        {
            try
            {
                return await _client.ChatAsync(request);
            }
            catch (AnthropicApiException ex)
            {
                _logger.LogError(ex, "Ошибка Anthropic API: {StatusCode} - {Message}", 
                    ex.StatusCode, ex.Message);
                
                // Логировать специальные детали ошибок для мониторинга
                if (ex.StatusCode == 529)
                {
                    _logger.LogWarning("Обнаружена перегрузка API - применяется стратегия задержки");
                }
                
                throw;
            }
        });
    }
}</code></pre>
                    </div>

                    

Шаблон Circuit Breaker

Реализуйте Circuit Breaker для защиты API:

// Реализация Circuit Breaker
public class AnthropicCircuitBreaker
{
    private readonly ILogger _logger;
    private readonly int _failureThreshold;
    private readonly TimeSpan _resetTimeout;
    
    private int _failureCount;
    private DateTime _lastFailureTime;
    private CircuitBreakerState _state = CircuitBreakerState.Closed;

    public AnthropicCircuitBreaker(ILogger logger, 
        int failureThreshold = 10, TimeSpan? resetTimeout = null)
    {
        _logger = logger;
        _failureThreshold = failureThreshold;
        _resetTimeout = resetTimeout ?? TimeSpan.FromMinutes(5);
    }

    public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
    {
        if (_state == CircuitBreakerState.Open)
        {
            if (DateTime.UtcNow - _lastFailureTime > _resetTimeout)
            {
                _logger.LogInformation("Достигнут таймаут Circuit Breaker, пытаемся закрыть");
                _state = CircuitBreakerState.HalfOpen;
            }
            else
            {
                throw new CircuitBreakerOpenException("Circuit Breaker открыт");
            }
        }

        try
        {
            var result = await action();
            
            if (_state == CircuitBreakerState.HalfOpen)
            {
                _logger.LogInformation("Circuit Breaker успешно закрыт");
                _state = CircuitBreakerState.Closed;
                _failureCount = 0;
            }
            
            return result;
        }
        catch (Exception ex)
        {
            _failureCount++;
            _lastFailureTime = DateTime.UtcNow;
            
            if (_failureCount >= _failureThreshold)
            {
                _logger.LogWarning("Circuit Breaker открыт после {FailureCount} сбоев", _failureCount);
                _state = CircuitBreakerState.Open;
            }
            
            throw;
        }
    }
}

// Реализация контроллера с Circuit Breaker
[HttpPost("chat-with-retry")]
public async Task<ActionResult<ChatResponse>> ChatWithRetry([FromBody] ChatRequest request)
{
    try
    {
        var response = await _anthropicService.ChatWithRetryAsync(request);
        return Ok(response);
    }
    catch (CircuitBreakerOpenException)
    {
        return StatusCode(503, new { error = "Служба временно недоступна из-за высокой частоты ошибок" });
    }
    catch (AnthropicServiceUnavailableException ex)
    {
        return StatusCode(503, new { error = ex.Message });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Неожиданная ошибка в службе чата");
        return StatusCode(500, new { error = "Внутренняя ошибка сервера" });
    }
}</code></pre>
                    </div>

                    

Конфигурация определения намерения

// Настроить определение намерения
services.AddSmartRAG(options =>
{
    options.AIProvider = AIProvider.Anthropic;
    options.StorageProvider = StorageProvider.Qdrant;
    options.ApiKey = "your-api-key";
    
    // Включить умное определение намерения запроса
    options.EnableQueryIntentDetection = true;
    options.IntentDetectionThreshold = 0.7; // Порог доверия
    options.LanguageAgnostic = true; // Работает с любым языком
});

// Использовать в вашем контроллере
[HttpPost("query")]
public async Task<ActionResult<QueryResult>> ProcessQuery([FromBody] QueryRequest request)
{
    var result = await _queryProcessor.ProcessQueryAsync(request.Query);
    return Ok(result);
}

Пакетная обработка документов

[HttpPost("upload-batch")]
public async Task<ActionResult<BatchUploadResult>> UploadBatchDocuments(IFormFileCollection files)
{
    try
    {
        var results = new List<Document>();
        var errors = new List<string>();

        foreach (var file in files)
        {
            try
            {
                var document = await _documentService.UploadDocumentAsync(file);
                results.Add(document);
                _logger.LogInformation("Документ {FileName} успешно обработан", file.FileName);
            }
            catch (Exception ex)
            {
                var error = $"Ошибка при обработке {file.FileName}: {ex.Message}";
                errors.Add(error);
                _logger.LogWarning(ex, "Ошибка при обработке {FileName}", file.FileName);
            }
        }

        return Ok(new BatchUploadResult
        {
            SuccessfulUploads = results,
            Errors = errors,
            TotalFiles = files.Count,
            SuccessCount = results.Count,
            ErrorCount = errors.Count
        });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Ошибка при пакетной обработке");
        return StatusCode(500, "Внутренняя ошибка сервера");
    }
}

public class BatchUploadResult
{
    public List<Document> SuccessfulUploads { get; set; }
    public List<string> Errors { get; set; }
    public int TotalFiles { get; set; }
    public int SuccessCount { get; set; }
    public int ErrorCount { get; set; }
}

Расширенный поиск с фильтрами

[HttpPost("advanced-search")]
public async Task<ActionResult<AdvancedSearchResult>> AdvancedSearch([FromBody] AdvancedSearchRequest request)
{
    try
    {
        var searchResults = await _documentService.SearchDocumentsAsync(request.Query, request.MaxResults);
        
        // Применение дополнительных фильтров
        var filteredResults = searchResults
            .Where(r => request.MinSimilarityScore == null || r.SimilarityScore >= request.MinSimilarityScore)
            .Where(r => request.ContentTypes == null || !request.ContentTypes.Any() || 
                       request.ContentTypes.Contains(r.Document.ContentType))
            .OrderByDescending(r => r.SimilarityScore)
            .ToList();

        var result = new AdvancedSearchResult
        {
            Query = request.Query,
            Results = filteredResults,
            TotalResults = filteredResults.Count,
            SearchTime = DateTime.UtcNow,
            AppliedFilters = new
            {
                MinSimilarityScore = request.MinSimilarityScore,
                ContentTypes = request.ContentTypes
            }
        };

        _logger.LogInformation("Расширенный поиск по '{Query}' дал {Count} отфильтрованных результатов", 
            request.Query, filteredResults.Count);
        
        return Ok(result);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Ошибка при расширенном поиске");
        return StatusCode(500, "Внутренняя ошибка сервера");
    }
}

public class AdvancedSearchRequest
{
    public string Query { get; set; }
    public int MaxResults { get; set; } = 10;
    public float? MinSimilarityScore { get; set; }
    public List<string> ContentTypes { get; set; }
}

public class AdvancedSearchResult
{
    public string Query { get; set; }
    public List<DocumentChunk> Results { get; set; }
    public int TotalResults { get; set; }
    public DateTime SearchTime { get; set; }
    public object AppliedFilters { get; set; }
}
</div> </div> </section>

Примеры Web API

Полные Web API контроллеры со всеми функциями SmartRAG.

Полный Document Controller

[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class DocumentController : ControllerBase
{
    private readonly IDocumentService _documentService;
    private readonly ILogger<DocumentController> _logger;

    public DocumentController(IDocumentService documentService, ILogger<DocumentController> logger)
    {
        _documentService = documentService;
        _logger = logger;
    }

    /// 
    /// Загружает документ
    /// 
    [HttpPost("upload")]
    [ProducesResponseType(typeof(Document), 200)]
    [ProducesResponseType(400)]
    [ProducesResponseType(500)]
    public async Task<ActionResult<Document>> UploadDocument(IFormFile file)
    {
        // Реализация см. выше
    }

    /// 
    /// Получает документ по ID
    /// 
    [HttpGet("{id}")]
    [ProducesResponseType(typeof(Document), 200)]
    [ProducesResponseType(404)]
    public async Task<ActionResult<Document>> GetDocument(string id)
    {
        try
        {
            var document = await _documentService.GetDocumentByIdAsync(id);
            if (document == null)
                return NotFound($"Документ с ID {id} не найден");

            return Ok(document);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Ошибка при получении документа {Id}", id);
            return StatusCode(500, "Внутренняя ошибка сервера");
        }
    }

    /// 
    /// Получает все документы
    /// 
    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<Document>), 200)]
    public async Task<ActionResult<IEnumerable<Document>>> GetAllDocuments()
    {
        try
        {
            var documents = await _documentService.GetAllDocumentsAsync();
            return Ok(documents);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Ошибка при получении всех документов");
            return StatusCode(500, "Внутренняя ошибка сервера");
        }
    }

    /// 
    /// Удаляет документ
    /// 
    [HttpDelete("{id}")]
    [ProducesResponseType(200)]
    [ProducesResponseType(404)]
    public async Task<ActionResult> DeleteDocument(string id)
    {
        try
        {
            var success = await _documentService.DeleteDocumentAsync(id);
            if (!success)
                return NotFound($"Документ с ID {id} не найден");

            _logger.LogInformation("Документ {Id} успешно удален", id);
            return Ok();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Ошибка при удалении документа {Id}", id);
            return StatusCode(500, "Внутренняя ошибка сервера");
        }
    }

    /// 
    /// Ищет в документах
    /// 
    [HttpGet("search")]
    [ProducesResponseType(typeof(IEnumerable<DocumentChunk>), 200)]
    public async Task<ActionResult<IEnumerable<DocumentChunk>>> SearchDocuments(
        [FromQuery] string query, 
        [FromQuery] int maxResults = 10)
    {
        // Реализация см. выше
    }

    /// 
    /// Генерирует RAG ответ
    /// 
    [HttpPost("ask")]
    [ProducesResponseType(typeof(RagResponse), 200)]
    public async Task<ActionResult<RagResponse>> AskQuestion([FromBody] AskQuestionRequest request)
    {
        // Реализация см. выше
    }
}

Пример консольного приложения

Простое консольное приложение с интеграцией SmartRAG.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SmartRAG.Interfaces;

namespace SmartRAG.ConsoleApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            
            using var scope = host.Services.CreateScope();
            var documentService = scope.ServiceProvider.GetRequiredService<IDocumentService>();
            var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();

            logger.LogInformation("Консольное приложение SmartRAG запущено");

            while (true)
            {
                Console.WriteLine("\n=== Консольное приложение SmartRAG ===");
                Console.WriteLine("1. Загрузить документ");
                Console.WriteLine("2. Поиск документов");
                Console.WriteLine("3. Задать вопрос");
                Console.WriteLine("4. Выход");
                Console.Write("Выберите опцию (1-4): ");

                var choice = Console.ReadLine();

                try
                {
                    switch (choice)
                    {
                        case "1":
                            await UploadDocument(documentService, logger);
                            break;
                        case "2":
                            await SearchDocuments(documentService, logger);
                            break;
                        case "3":
                            await AskQuestion(documentService, logger);
                            break;
                        case "4":
                            logger.LogInformation("Приложение завершается");
                            return;
                        default:
                            Console.WriteLine("Неверная опция. Пожалуйста, выберите 1-4.");
                            break;
                    }
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Ошибка при выполнении");
                    Console.WriteLine($"Ошибка: {ex.Message}");
                }
            }
        }

        static async Task UploadDocument(IDocumentService documentService, ILogger logger)
        {
            Console.Write("Введите путь к файлу: ");
            var filePath = Console.ReadLine();

            if (!File.Exists(filePath))
            {
                Console.WriteLine("Файл не найден!");
                return;
            }

            var fileInfo = new FileInfo(filePath);
            var fileBytes = await File.ReadAllBytesAsync(filePath);
            var fileStream = new MemoryStream(fileBytes);
            var formFile = new FormFile(fileStream, 0, fileBytes.Length, "file", fileInfo.Name);

            var document = await documentService.UploadDocumentAsync(formFile);
            Console.WriteLine($"Документ успешно загружен: {document.Id}");
        }

        static async Task SearchDocuments(IDocumentService documentService, ILogger logger)
        {
            Console.Write("Введите поисковый запрос: ");
            var query = Console.ReadLine();

            var results = await documentService.SearchDocumentsAsync(query, 5);
            
            Console.WriteLine($"\nНайденные результаты ({results.Count()}):");
            foreach (var result in results)
            {
                Console.WriteLine($"- {result.Document.FileName} (Сходство: {result.SimilarityScore:P2})");
                Console.WriteLine($"  {result.Content.Substring(0, Math.Min(100, result.Content.Length))}...");
            }
        }

        static async Task AskQuestion(IDocumentService documentService, ILogger logger)
        {
            Console.Write("Задайте ваш вопрос: ");
            var question = Console.ReadLine();

            var response = await documentService.GenerateRagAnswerAsync(question, 5);
            
            Console.WriteLine($"\nОтвет: {response.Answer}");
            Console.WriteLine($"\nИсточники ({response.Sources.Count}):");
            foreach (var source in response.Sources)
            {
                Console.WriteLine($"- {source.DocumentName} (Сходство: {source.SimilarityScore:P2})");
            }
        }

        static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((context, services) =>
                {
                    services.AddSmartRAG(context.Configuration, options =>
                    {
                        options.AIProvider = AIProvider.Anthropic;
                        options.StorageProvider = StorageProvider.Qdrant;
                    });
                });
    }
}

Нужна помощь?

Для дополнительных примеров и поддержки:

</div> </div>