diff --git a/CodeBase/CodeBase.csproj b/CodeBase/CodeBase.csproj
new file mode 100644
index 0000000..8718518
--- /dev/null
+++ b/CodeBase/CodeBase.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
diff --git a/CodeBase/CodeBase.http b/CodeBase/CodeBase.http
new file mode 100644
index 0000000..0feaa0d
--- /dev/null
+++ b/CodeBase/CodeBase.http
@@ -0,0 +1,6 @@
+@CodeBase_HostAddress = http://localhost:5236
+
+GET {{CodeBase_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/CodeBase/CodeBase.slnx b/CodeBase/CodeBase.slnx
new file mode 100644
index 0000000..540a350
--- /dev/null
+++ b/CodeBase/CodeBase.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/CodeBase/Controllers/CodeController.cs b/CodeBase/Controllers/CodeController.cs
new file mode 100644
index 0000000..73b3e71
--- /dev/null
+++ b/CodeBase/Controllers/CodeController.cs
@@ -0,0 +1,26 @@
+using CodeBase.Models;
+using CodeBase.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CodeBase.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class CodeController(CodeService service) : ControllerBase
+ {
+ [HttpPost("analyze")]
+ public async Task AnalyzeRepository(string path, string name)
+ {
+ var chunks = await service.GetCodeChunksAsync(path, name);
+ return chunks[0];
+ }
+ [HttpPost("answer")]
+ public async Task GetAnswer(string question, string name)
+ {
+ var answer = await service.GetAnswerAsync(name, question);
+ return answer;
+ }
+ }
+}
diff --git a/CodeBase/Models/CodeChunk.cs b/CodeBase/Models/CodeChunk.cs
new file mode 100644
index 0000000..3c65039
--- /dev/null
+++ b/CodeBase/Models/CodeChunk.cs
@@ -0,0 +1,12 @@
+namespace CodeBase.Models
+{
+ public class CodeChunk
+ {
+ public string FilePath { get; set; }
+ public string ClassName { get; set; }
+ public string MethodName { get; set; }
+ public string Documentation { get; set; }
+ public string Content { get; set; }
+ public float[] Vector { get; set; }
+ }
+}
diff --git a/CodeBase/Program.cs b/CodeBase/Program.cs
new file mode 100644
index 0000000..cb2d15a
--- /dev/null
+++ b/CodeBase/Program.cs
@@ -0,0 +1,33 @@
+using CodeBase.Services;
+using CodeBase.Warehouse;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddSingleton();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+builder.Services.AddScoped();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+
+builder.Services.AddControllers();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/CodeBase/Properties/launchSettings.json b/CodeBase/Properties/launchSettings.json
new file mode 100644
index 0000000..f0052b3
--- /dev/null
+++ b/CodeBase/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5236",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:7218;http://localhost:5236",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/CodeBase/Services/ChunkService.cs b/CodeBase/Services/ChunkService.cs
new file mode 100644
index 0000000..896c8d2
--- /dev/null
+++ b/CodeBase/Services/ChunkService.cs
@@ -0,0 +1,62 @@
+using CodeBase.Models;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace CodeBase.Services
+{
+ public class ChunkService(VectorizationService vectorizationService)
+ {
+ public List ChunkCSharpFile(string filePath, string fileContent)
+ {
+ var chunks = new List();
+ var syntaxTree = CSharpSyntaxTree.ParseText(fileContent);
+ var root = syntaxTree.GetRoot();
+
+ // 1. Собираем методы (как и раньше)
+ var methods = root.DescendantNodes().OfType();
+ foreach (var method in methods)
+ {
+ chunks.Add(new CodeChunk
+ {
+ FilePath = filePath,
+ MethodName = method.Identifier.Text,
+ Content = method.ToFullString().Trim()
+ });
+ }
+
+ // 2. ДОБАВЛЯЕМ СБОР ENUM (Перечислений)
+ var enums = root.DescendantNodes().OfType();
+ foreach (var enumSyntax in enums)
+ {
+ chunks.Add(new CodeChunk
+ {
+ FilePath = filePath,
+ MethodName = enumSyntax.Identifier.Text, // Сохраняем имя енама
+ Content = enumSyntax.ToFullString().Trim()
+ });
+ }
+
+ // 3. ДОБАВЛЯЕМ СБОР КЛАССОВ-МОДЕЛЕЙ (без методов)
+ var classes = root.DescendantNodes().OfType();
+ foreach (var classSyntax in classes)
+ {
+ // Берем только свойства, чтобы понимать структуру модели
+ var properties = classSyntax.Members.OfType();
+ if (properties.Any())
+ {
+ chunks.Add(new CodeChunk
+ {
+ FilePath = filePath,
+ MethodName = classSyntax.Identifier.Text,
+ // Сохраняем объявление класса и его свойства
+ Content = $"class {classSyntax.Identifier.Text} {{\n" +
+ string.Join("\n", properties.Select(p => p.ToFullString().Trim())) +
+ "\n}"
+ });
+ }
+ }
+
+ return chunks;
+ }
+ }
+}
diff --git a/CodeBase/Services/CodeService.cs b/CodeBase/Services/CodeService.cs
new file mode 100644
index 0000000..c381a59
--- /dev/null
+++ b/CodeBase/Services/CodeService.cs
@@ -0,0 +1,102 @@
+using CodeBase.Models;
+using CodeBase.Warehouse;
+
+namespace CodeBase.Services
+{
+ public class CodeService(ChunkService service,
+ ChunkWarehouse warehouse,
+ VectorizationService vectorizationService,
+ LlmService llmService)
+ {
+ public async Task> GetCodeChunksAsync(string path, string name)
+ {
+ if (!Path.Exists(path))
+ {
+ throw new Exception("Путь не найден");
+ }
+
+ var allChunks = new List();
+
+ // Находим все .cs файлы во всех вложенных папках
+ var csFiles = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories);
+
+ foreach (var filePath in csFiles)
+ {
+ var content = File.ReadAllText(filePath);
+
+ // Парсим каждый файл отдельно, сохраняя его оригинальный путь
+ var fileChunks = service.ChunkCSharpFile(filePath, content);
+
+ allChunks.AddRange(fileChunks);
+ }
+
+ var chunks = await vectorizationService.EnrichChunksWithVectorsAsync(allChunks);
+
+ warehouse.UpsertDictionary(name, allChunks);
+
+ return chunks;
+ }
+
+ public async Task GetAnswerAsync(string name, string question)
+ {
+ var query = await vectorizationService.GetVectorAsync(question);
+
+ var answerVectors = Search(query, name, 15);
+
+ var answer = await llmService.AskQuestionAsync(question, answerVectors.Select(s => s.Chunk).ToList());
+
+ return answer;
+ }
+
+ // Главный метод поиска
+ public List<(CodeChunk Chunk, float Score)> Search(
+ float[] queryVector,
+ string name,
+ int topK = 3) // Возвращаем топ-3 результата
+ {
+ var memoryBase = warehouse.GetChunks(name);
+
+ var results = new List<(CodeChunk, float)>();
+
+ foreach (var chunk in memoryBase)
+ {
+ if (chunk.Vector == null || chunk.Vector.Length == 0)
+ continue;
+
+ // Считаем близость вектора вопроса к вектору кода
+ float similarity = CalculateCosineSimilarity(queryVector, chunk.Vector);
+ results.Add((chunk, similarity));
+ }
+
+ // Сортируем по убыванию сходства (чем ближе к 1, тем лучше)
+ return results
+ .OrderByDescending(x => x.Item2)
+ .Take(topK)
+ .ToList();
+ }
+
+ // Математика косинусного сходства
+ private float CalculateCosineSimilarity(float[] vectorA, float[] vectorB)
+ {
+ if (vectorA.Length != vectorB.Length)
+ throw new ArgumentException("Векторы должны быть одинаковой длины (например, 768).");
+
+ float dotProduct = 0;
+ float magnitudeA = 0;
+ float magnitudeB = 0;
+
+ for (int i = 0; i < vectorA.Length; i++)
+ {
+ dotProduct += vectorA[i] * vectorB[i];
+ magnitudeA += vectorA[i] * vectorA[i];
+ magnitudeB += vectorB[i] * vectorB[i];
+ }
+
+ // Защита от деления на ноль
+ if (magnitudeA == 0 || magnitudeB == 0)
+ return 0;
+
+ return (float)(dotProduct / (Math.Sqrt(magnitudeA) * Math.Sqrt(magnitudeB)));
+ }
+ }
+}
diff --git a/CodeBase/Services/LlmPromptBuilder.cs b/CodeBase/Services/LlmPromptBuilder.cs
new file mode 100644
index 0000000..2321352
--- /dev/null
+++ b/CodeBase/Services/LlmPromptBuilder.cs
@@ -0,0 +1,41 @@
+using CodeBase.Models;
+using System.Collections.Generic;
+using System.Text;
+
+public class LlmPromptBuilder
+{
+ public string BuildPrompt(string userQuestion, List foundChunks)
+ {
+ var sb = new StringBuilder();
+
+ // 1. Задаем жесткую роль и ограничения
+ sb.AppendLine("Ты — опытный разработчик и архитектор. Твоя задача — ответить на вопрос пользователя.");
+ sb.AppendLine("ОТВЕЧАЙ СТРОГО НА ОСНОВЕ ПРЕДОСТАВЛЕННОГО КОДА НИЖЕ.");
+ sb.AppendLine("Если в коде нет ответа на вопрос, честно скажи: «В данном фрагменте кода нет этой информации». Не придумывай функции, которых нет в тексте.");
+
+ sb.AppendLine("\n================ ПРЕДОСТАВЛЕННЫЙ КОД ================");
+
+ // 2. Вклеиваем найденные чанки с контекстом
+ foreach (var chunk in foundChunks)
+ {
+ sb.AppendLine($"Файл: {chunk.FilePath}");
+ sb.AppendLine($"Класс: {chunk.ClassName}");
+ sb.AppendLine($"Метод: {chunk.MethodName}");
+ if (!string.IsNullOrEmpty(chunk.Documentation))
+ {
+ sb.AppendLine($"Документация: {chunk.Documentation}");
+ }
+ sb.AppendLine("Код:");
+ sb.AppendLine("```csharp");
+ sb.AppendLine(chunk.Content);
+ sb.AppendLine("```");
+ sb.AppendLine("--------------------------------------------------");
+ }
+
+ // 3. Добавляем сам вопрос
+ sb.AppendLine("\n================ ВОПРОС ПОЛЬЗОВАТЕЛЯ ================");
+ sb.AppendLine(userQuestion);
+
+ return sb.ToString();
+ }
+}
\ No newline at end of file
diff --git a/CodeBase/Services/LlmService.cs b/CodeBase/Services/LlmService.cs
new file mode 100644
index 0000000..ec3e797
--- /dev/null
+++ b/CodeBase/Services/LlmService.cs
@@ -0,0 +1,62 @@
+using CodeBase.Models;
+using OpenAI;
+using OpenAI.Chat;
+using System;
+using System.ClientModel;
+using System.Threading.Tasks;
+
+public class LlmService
+{
+ private readonly ChatClient _chatClient;
+ private readonly LlmPromptBuilder _builder;
+
+ public LlmService(LlmPromptBuilder llmPromptBuilder)
+ {
+ // 1. Указываем адрес Ollama (обязательно с /v1 на конце для совместимости с OpenAI)
+ var options = new OpenAIClientOptions
+ {
+ Endpoint = new Uri("http://localhost:11434/v1")
+ };
+
+ // 2. Ключ не нужен, но пакет просит хоть какую-то строку
+ var credential = new ApiKeyCredential("ollama");
+
+ // 3. ВАЖНО: Имя модели должно ТОЧНО совпадать с тем, что ты скачала в Ollama.
+ // Например, "llama3", "llama3.1", "qwen2.5-coder" или "phi3".
+ string modelName = "qwen2.5-coder:7b";
+
+ _chatClient = new ChatClient(modelName, credential, options);
+
+ _builder = llmPromptBuilder;
+ }
+
+ public async Task AskQuestionAsync(string userQuestion, List foundChunks)
+ {
+ try
+ {
+ var finalPrompt = _builder.BuildPrompt(userQuestion, foundChunks);
+ // Формируем сообщение. Поскольку мы уже зашили роль и инструкции
+ // внутрь finalPrompt с помощью LlmPromptBuilder,
+ // передаем все это как UserMessage.
+ var messages = new ChatMessage[]
+ {
+ new UserChatMessage(finalPrompt)
+ };
+
+ // Отправляем запрос (настройки опциональны)
+ var completionOptions = new ChatCompletionOptions
+ {
+ Temperature = 0.2f, // Делаем ответы менее креативными и более точными
+ };
+
+ ChatCompletion completion = await _chatClient.CompleteChatAsync(messages, completionOptions);
+
+ // Возвращаем сгенерированный текст
+ return completion.Content[0].Text;
+ }
+ catch (Exception ex)
+ {
+ return $"[-] Ошибка при обращении к LLM: {ex.Message}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/CodeBase/Services/VectorizationService.cs b/CodeBase/Services/VectorizationService.cs
new file mode 100644
index 0000000..3bb40a2
--- /dev/null
+++ b/CodeBase/Services/VectorizationService.cs
@@ -0,0 +1,92 @@
+using CodeBase.Models;
+
+namespace CodeBase.Services
+{
+ public class VectorizeRequest
+ {
+ public string text { get; set; }
+ }
+
+ public class VectorizeResponse
+ {
+ public float[] vector { get; set; }
+ }
+
+ public class VectorizationService
+ {
+ private readonly HttpClient _httpClient;
+
+ public VectorizationService()
+ {
+ _httpClient = new HttpClient();
+ // Адрес нашего локального Python-сервиса
+ _httpClient.BaseAddress = new Uri("http://localhost:8000/");
+ }
+
+
+ public async Task GetVectorAsync(string text)
+ {
+ var requestBody = new VectorizeRequest { text = text };
+
+ try
+ {
+ // Стучимся на наш Python-сервер (порт 8000)
+ var response = await _httpClient.PostAsJsonAsync("vectorize", requestBody);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var result = await response.Content.ReadFromJsonAsync();
+ return result?.vector;
+ }
+
+ Console.WriteLine($"[-] Ошибка API при векторизации вопроса: {response.StatusCode}");
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[-] Ошибка подключения к Python-сервису: {ex.Message}");
+ return null;
+ }
+ }
+
+ public async Task> EnrichChunksWithVectorsAsync(List chunks)
+ {
+ foreach (var chunk in chunks)
+ {
+ // 1. Склеиваем контекст
+ // Мы даем нейросети подсказку о том, где именно лежит этот код
+ string contextText = $"File: {chunk.FilePath}\nClass: {chunk.ClassName}\nMethod: {chunk.MethodName}\nCode:\n{chunk.Content}";
+
+ var requestBody = new VectorizeRequest { text = contextText };
+
+ try
+ {
+ // 2. Стучимся в Python
+ var response = await _httpClient.PostAsJsonAsync("vectorize", requestBody);
+
+ if (response.IsSuccessStatusCode)
+ {
+ // 3. Достаем массив чисел (обычно 768 элементов для GraphCodeBERT)
+ var result = await response.Content.ReadFromJsonAsync();
+ if (result != null && result.vector != null)
+ {
+ // 4. Сохраняем вектор прямо в наш объект в памяти
+ chunk.Vector = result.vector;
+ Console.WriteLine($"[+] Векторизован метод: {chunk.MethodName}");
+ }
+ }
+ else
+ {
+ Console.WriteLine($"[-] Ошибка API для {chunk.MethodName}: {response.StatusCode}");
+ }
+
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[-] Ошибка подключения к Python-сервису: {ex.Message}");
+ }
+ }
+ return chunks;
+ }
+ }
+}
diff --git a/CodeBase/Warehouse/ChunkWarehouse.cs b/CodeBase/Warehouse/ChunkWarehouse.cs
new file mode 100644
index 0000000..9d1e150
--- /dev/null
+++ b/CodeBase/Warehouse/ChunkWarehouse.cs
@@ -0,0 +1,42 @@
+using CodeBase.Models;
+
+namespace CodeBase.Warehouse
+{
+ public class ChunkWarehouse
+ {
+ public Dictionary> dictionary;
+
+ public ChunkWarehouse()
+ {
+ this.dictionary = new();
+ }
+
+ public void UpsertDictionary(string name, List chunks)
+ {
+ if (dictionary.ContainsKey(name))
+ {
+ dictionary[name] = chunks;
+ }
+ else dictionary.TryAdd(name, chunks);
+ }
+
+ public void DeleteElement(string name)
+ {
+ dictionary.Remove(name);
+ }
+
+ public List GetChunks(string name)
+ {
+ if(dictionary.TryGetValue(name, out var chunks))
+ {
+ return chunks;
+ }
+ throw new Exception("Такого проекта нет");
+ }
+
+ public void ClearDictionary()
+ {
+ dictionary.Clear();
+ }
+ }
+}
diff --git a/CodeBase/appsettings.Development.json b/CodeBase/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/CodeBase/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/CodeBase/appsettings.json b/CodeBase/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/CodeBase/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/PythonProject/.idea/.gitignore b/PythonProject/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/PythonProject/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/PythonProject/.idea/PythonProject.iml b/PythonProject/.idea/PythonProject.iml
new file mode 100644
index 0000000..74d515a
--- /dev/null
+++ b/PythonProject/.idea/PythonProject.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PythonProject/.idea/inspectionProfiles/profiles_settings.xml b/PythonProject/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/PythonProject/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PythonProject/.idea/misc.xml b/PythonProject/.idea/misc.xml
new file mode 100644
index 0000000..ad62bdc
--- /dev/null
+++ b/PythonProject/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PythonProject/.idea/modules.xml b/PythonProject/.idea/modules.xml
new file mode 100644
index 0000000..5f43230
--- /dev/null
+++ b/PythonProject/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PythonProject/main.py b/PythonProject/main.py
new file mode 100644
index 0000000..090ab4f
--- /dev/null
+++ b/PythonProject/main.py
@@ -0,0 +1,30 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+from transformers import AutoTokenizer, AutoModel
+import torch
+import uvicorn # <-- Добавили импорт
+
+app = FastAPI(title="GraphCodeBERT Vectorizer")
+
+# Загружаем модель глобально при старте приложения
+model_name = "microsoft/graphcodebert-base"
+tokenizer = AutoTokenizer.from_pretrained(model_name)
+model = AutoModel.from_pretrained(model_name)
+
+class ChunkRequest(BaseModel):
+ text: str
+
+@app.post("/vectorize")
+def vectorize(request: ChunkRequest):
+ inputs = tokenizer(request.text, return_tensors="pt", truncation=True, max_length=512)
+
+ with torch.no_grad():
+ outputs = model(**inputs)
+
+ vector = outputs.last_hidden_state[:, 0, :].squeeze().tolist()
+ return {"vector": vector}
+
+# <-- Добавили блок запуска
+if __name__ == "__main__":
+ print("Запускаем сервер на порту 8000...")
+ uvicorn.run(app, host="0.0.0.0", port=8000)
\ No newline at end of file
diff --git a/PythonProject/requirements.txt b/PythonProject/requirements.txt
new file mode 100644
index 0000000..4df094f
--- /dev/null
+++ b/PythonProject/requirements.txt
@@ -0,0 +1,5 @@
+fastapi
+uvicorn
+transformers
+torch
+pydantic
\ No newline at end of file