diff --git a/CodeBase/CodeBase.csproj b/CodeBase/CodeBase.csproj
index cd5424c..6bb0181 100644
--- a/CodeBase/CodeBase.csproj
+++ b/CodeBase/CodeBase.csproj
@@ -8,9 +8,13 @@
+
+
+
+
+
-
diff --git a/CodeBase/Models/CodeChunk.cs b/CodeBase/Models/CodeChunk.cs
index 3c65039..ad3e3e5 100644
--- a/CodeBase/Models/CodeChunk.cs
+++ b/CodeBase/Models/CodeChunk.cs
@@ -8,5 +8,6 @@
public string Documentation { get; set; }
public string Content { get; set; }
public float[] Vector { get; set; }
+ public List OutgoingCalls { get; set; } = new List();
}
}
diff --git a/CodeBase/Models/GraphNodeContext.cs b/CodeBase/Models/GraphNodeContext.cs
new file mode 100644
index 0000000..6c3fa76
--- /dev/null
+++ b/CodeBase/Models/GraphNodeContext.cs
@@ -0,0 +1,17 @@
+namespace CodeBase.Models
+{
+ public class GraphNodeContext
+ {
+ public string MethodName { get; set; }
+ public string FilePath { get; set; }
+ public string Content { get; set; }
+
+ // Графовые связи, которые мы вытащим из Neo4j
+
+ // Кого вызывает этот метод (и какие классы/енумы использует)
+ public List OutgoingDependencies { get; set; } = new();
+
+ // Кто вызывает этот метод (кто от него зависит)
+ public List IncomingDependencies { get; set; } = new();
+ }
+}
diff --git a/CodeBase/Program.cs b/CodeBase/Program.cs
index c583cda..bebe296 100644
--- a/CodeBase/Program.cs
+++ b/CodeBase/Program.cs
@@ -1,20 +1,28 @@
using CodeBase.Services;
-using CodeBase.Warehouse;
-using Qdrant.Client;
-using Qdrant.Client.Grpc;
+using Microsoft.Build.Locator;
+using Neo4j.Driver;
+
+if (!MSBuildLocator.IsRegistered)
+{
+ MSBuildLocator.RegisterDefaults();
+}
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
-builder.Services.AddSingleton(new QdrantClient("localhost", 6334));
+builder.Services.AddSingleton(sp =>
+ GraphDatabase.Driver(
+ "bolt://localhost:7687",
+ AuthTokens.Basic("neo4j", "password123")
+ )
+);
-builder.Services.AddSingleton();
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.AddScoped();
builder.Services.AddTransient();
builder.Services.AddTransient();
-builder.Services.AddTransient();
+builder.Services.AddTransient();
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
@@ -34,27 +42,12 @@ app.UseAuthorization();
app.MapControllers();
-
using (var scope = app.Services.CreateScope())
{
- var qdrantClient = scope.ServiceProvider.GetRequiredService();
-
- // Получаем список существующих коллекций
- var collections = await qdrantClient.ListCollectionsAsync();
-
- if (!collections.Contains("CodeBaseCollection"))
- {
- Console.WriteLine("Создаем новую коллекцию в Qdrant...");
- await qdrantClient.CreateCollectionAsync(
- collectionName: "CodeBaseCollection",
- vectorsConfig: new VectorParams
- {
- Size = 768, // Размер вектора GraphCodeBERT
- Distance = Distance.Cosine // Сразу задаем алгоритм поиска (косинусное сходство)
- }
- );
- }
+ var graphRepo = scope.ServiceProvider.GetRequiredService();
+ Console.WriteLine("Проверяем и создаем векторный индекс в Neo4j...");
+ await graphRepo.InitializeDbAsync();
+ Console.WriteLine("Индекс готов!");
}
-
app.Run();
diff --git a/CodeBase/Repositories/GraphRepository.cs b/CodeBase/Repositories/GraphRepository.cs
new file mode 100644
index 0000000..85f87fa
--- /dev/null
+++ b/CodeBase/Repositories/GraphRepository.cs
@@ -0,0 +1,139 @@
+using CodeBase.Models;
+using Neo4j.Driver;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+public class GraphRepository
+{
+ private readonly IDriver _driver;
+
+ public GraphRepository(IDriver driver)
+ {
+ _driver = driver;
+ }
+
+ // ==========================================
+ // 0. ИНИЦИАЛИЗАЦИЯ (Создаем пространство для векторов)
+ // ==========================================
+ public async Task InitializeDbAsync()
+ {
+ await using var session = _driver.AsyncSession();
+
+ await session.ExecuteWriteAsync(async tx =>
+ {
+ // Говорим базе: "Создай индекс для поиска по сходству, если его еще нет.
+ // Размер вектора 768 (GraphCodeBERT), алгоритм - косинусное расстояние"
+ await tx.RunAsync(@"
+ CREATE VECTOR INDEX code_vectors IF NOT EXISTS
+ FOR (c:CodeChunk) ON (c.vector)
+ OPTIONS { indexConfig: {
+ `vector.dimensions`: 768,
+ `vector.similarity_function`: 'cosine'
+ }}"
+ );
+ });
+ }
+
+ // ==========================================
+ // 1. СОХРАНЕНИЕ УЗЛОВ И ВЕКТОРОВ
+ // ==========================================
+ public async Task SaveChunksAsync(string projectName, List chunks)
+ {
+ await using var session = _driver.AsyncSession();
+
+ // 1. Делаем данные "безопасными" для базы
+ var parameters = chunks.Where(c => c.Vector != null).Select(chunk => new
+ {
+ id = Guid.NewGuid().ToString(),
+ projectName = projectName,
+ filePath = chunk.FilePath,
+ methodName = chunk.MethodName,
+ content = chunk.Content,
+ vector = chunk.Vector,
+ // Защита от null и пустых строк (база их не переварит в цикле FOREACH)
+ outgoingCalls = chunk.OutgoingCalls != null
+ ? chunk.OutgoingCalls.Where(x => !string.IsNullOrWhiteSpace(x)).ToList()
+ : new List()
+ }).ToList();
+
+ await session.ExecuteWriteAsync(async tx =>
+ {
+ await tx.RunAsync(@"
+ UNWIND $batch AS chunk
+
+ // Создаем или обновляем основной метод
+ MERGE (c:CodeChunk { methodName: chunk.methodName })
+ SET c.id = chunk.id,
+ c.projectName = chunk.projectName,
+ c.filePath = chunk.filePath,
+ c.content = chunk.content,
+ c.vector = chunk.vector
+
+ // Рисуем связи только для валидных вызовов
+ FOREACH (calledMethod IN chunk.outgoingCalls |
+ MERGE (target:CodeChunk { methodName: calledMethod })
+ MERGE (c)-[:CALLS]->(target)
+ )
+ ", new { batch = parameters });
+ });
+ }
+
+ // ==========================================
+ // 2. ПОИСК (Пока только по вектору, связи добавим позже)
+ // ==========================================
+ public async Task> SearchAsync(string projectName, float[] queryVector, int topK = 15)
+ {
+ await using var session = _driver.AsyncSession();
+
+ return await session.ExecuteReadAsync(async tx =>
+ {
+ var cursor = await tx.RunAsync(@"
+ // 1. Ищем самые подходящие узлы по векторному сходству
+ CALL db.index.vector.queryNodes('code_vectors', $topK, $queryVector)
+ YIELD node AS c, score
+ WHERE c.projectName = $projectName
+
+ // 2. Ищем стрелочки ВНИЗ (Кого вызывает этот метод?)
+ // OPTIONAL MATCH гарантирует, что мы не потеряем узел, если связей нет
+ OPTIONAL MATCH (c)-[:CALLS]->(out:CodeChunk)
+ // Собираем имена вызываемых методов в массив
+ WITH c, score, collect(DISTINCT out.methodName) AS outgoingDependencies
+
+ // 3. Ищем стрелочки ВВЕРХ (Кто вызывает этот метод?)
+ OPTIONAL MATCH (in:CodeChunk)-[:CALLS]->(c)
+ WITH c, score, outgoingDependencies, collect(DISTINCT in.methodName) AS incomingDependencies
+
+ // 4. Возвращаем готовую структуру для C#
+ RETURN
+ c.methodName AS methodName,
+ c.filePath AS filePath,
+ c.content AS content,
+ outgoingDependencies,
+ incomingDependencies,
+ score
+ ORDER BY score DESC
+ ", new { topK, queryVector, projectName });
+
+ var results = new List();
+
+ while (await cursor.FetchAsync())
+ {
+ var record = cursor.Current;
+ results.Add(new GraphNodeContext
+ {
+ MethodName = record["methodName"].As(),
+ FilePath = record["filePath"].As(),
+ Content = record["content"].As(),
+
+ // Драйвер Neo4j возвращает массивы как IList