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, + // поэтому аккуратно кастуем их в наши списки строк + OutgoingDependencies = record["outgoingDependencies"].As>().ToList(), + IncomingDependencies = record["incomingDependencies"].As>().ToList() + }); + } + return results; + }); + } +} \ No newline at end of file diff --git a/CodeBase/Repositories/QdrantRepository.cs b/CodeBase/Repositories/QdrantRepository.cs deleted file mode 100644 index 7db4408..0000000 --- a/CodeBase/Repositories/QdrantRepository.cs +++ /dev/null @@ -1,89 +0,0 @@ -using CodeBase.Models; -using Qdrant.Client; -using Qdrant.Client.Grpc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -public class QdrantRepository -{ - private readonly QdrantClient _client; - private const string CollectionName = "CodeBaseCollection"; - - public QdrantRepository(QdrantClient client) - { - _client = client; - } - - // ========================================== - // 1. СОХРАНЕНИЕ ВЕКТОРОВ С ПРИВЯЗКОЙ К ПРОЕКТУ - // ========================================== - public async Task SaveChunksAsync(string projectName, List chunks) - { - var points = new List(); - - foreach (var chunk in chunks) - { - if (chunk.Vector == null) continue; - - // Формируем точку для БД - var point = new PointStruct - { - Id = Guid.NewGuid(), // Уникальный ID чанка - Vectors = chunk.Vector, // Тот самый массив float[] - - // Вот здесь мы навешиваем все ярлыки! - Payload = - { - ["ProjectName"] = projectName, - ["FilePath"] = chunk.FilePath, - ["MethodName"] = chunk.MethodName, - ["Content"] = chunk.Content // Сам текст кода тоже храним прямо в БД - } - }; - points.Add(point); - } - - // Отправляем пачку векторов в базу - await _client.UpsertAsync(CollectionName, points); - } - - // ========================================== - // 2. ПОИСК ТОЛЬКО В РАМКАХ ОДНОГО ПРОЕКТА - // ========================================== - public async Task> SearchAsync(string projectName, float[] queryVector, int topK = 15) - { - // Создаем фильтр: искать только там, где ProjectName совпадает с запрошенным - var projectFilter = new Filter - { - Must = { - new Condition - { - Field = new FieldCondition - { - Key = "ProjectName", - Match = new Match { Keyword = projectName } - } - } - } - }; - - // База делает всю тяжелую математику за нас! - var searchResults = await _client.SearchAsync( - collectionName: CollectionName, - vector: queryVector, - limit: (ulong)topK, - filter: projectFilter, // Передаем наш фильтр - payloadSelector: true // Просим БД вернуть Payload вместе с результатами - ); - - // Превращаем ответы базы обратно в наши C#-объекты CodeChunk - return searchResults.Select(hit => new CodeChunk - { - FilePath = hit.Payload["FilePath"].StringValue, - MethodName = hit.Payload["MethodName"].StringValue, - Content = hit.Payload["Content"].StringValue - }).ToList(); - } -} \ No newline at end of file diff --git a/CodeBase/Services/ChunkService.cs b/CodeBase/Services/ChunkService.cs index 896c8d2..dcddcd3 100644 --- a/CodeBase/Services/ChunkService.cs +++ b/CodeBase/Services/ChunkService.cs @@ -1,6 +1,8 @@ using CodeBase.Models; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.MSBuild; namespace CodeBase.Services { @@ -12,16 +14,51 @@ namespace CodeBase.Services var syntaxTree = CSharpSyntaxTree.ParseText(fileContent); var root = syntaxTree.GetRoot(); + var compilation = CSharpCompilation.Create("MyAnalysis") + .AddSyntaxTrees(syntaxTree) + // Подкидываем базовые библиотеки .NET, чтобы он узнал System.Linq и прочее + .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)); + + var semanticModel = compilation.GetSemanticModel(syntaxTree); + // 1. Собираем методы (как и раньше) var methods = root.DescendantNodes().OfType(); foreach (var method in methods) { - chunks.Add(new CodeChunk + var chunk = new CodeChunk { FilePath = filePath, MethodName = method.Identifier.Text, Content = method.ToFullString().Trim() - }); + }; + + var invocations = method.DescendantNodes().OfType(); + + foreach (var invocation in invocations) + { + var symbolInfo = semanticModel.GetSymbolInfo(invocation); + + if (symbolInfo.Symbol is IMethodSymbol methodSymbol) + { + // Получаем полный путь пространства имен (например, "System.Linq") + string namespaceName = methodSymbol.ContainingNamespace.ToString(); + + // Пропускаем все системные вызовы .NET + if (namespaceName.StartsWith("System") || namespaceName.StartsWith("Microsoft")) + { + continue; + } + + // Если это наш метод (например, "Bas.Core.Services"), добавляем его + chunk.OutgoingCalls.Add(methodSymbol.Name); + Console.WriteLine($"[ПАРСЕР] Добавлена бизнес-связь -> {methodSymbol.Name}"); + } + } + + chunk.OutgoingCalls = chunk.OutgoingCalls.Distinct().ToList(); + + chunks.Add(chunk); } // 2. ДОБАВЛЯЕМ СБОР ENUM (Перечислений) @@ -58,5 +95,94 @@ namespace CodeBase.Services return chunks; } + + public async Task> ParseProjectAsync(string csprojPath) + { + // 1. Создаем воркспейс и загружаем проект (это может занять пару секунд) + using var workspace = MSBuildWorkspace.Create(); + Console.WriteLine("Загружаем проект и строим семантическую модель..."); + var project = await workspace.OpenProjectAsync(csprojPath); + + var chunksList = new List(); + + // 2. Проходимся по всем C#-файлам в проекте + foreach (var document in project.Documents) + { + // Теперь у нас есть ГАРАНТИРОВАННАЯ семантическая модель для каждого файла + var semanticModel = await document.GetSemanticModelAsync(); + var syntaxTree = await document.GetSyntaxTreeAsync(); + var root = await syntaxTree.GetRootAsync(); + + var methods = root.DescendantNodes().OfType(); + + foreach (var method in methods) + { + var chunk = new CodeChunk + { + MethodName = method.Identifier.Text, + Content = method.ToFullString(), + FilePath = document.FilePath + }; + + var invocations = method.DescendantNodes().OfType(); + + foreach (var invocation in invocations) + { + // 3. Просим у модели 100% точную информацию о вызываемом методе + var symbolInfo = semanticModel.GetSymbolInfo(invocation); + + if (symbolInfo.Symbol is IMethodSymbol methodSymbol) + { + // Получаем пространство имен метода (например, "System.Linq" или "CodeBase.Services") + string namespaceName = methodSymbol.ContainingNamespace?.ToString() ?? ""; + + // Отсеиваем только системные вызовы, оставляя ВЕСЬ бизнес-код + if (!namespaceName.StartsWith("System") && !namespaceName.StartsWith("Microsoft")) + { + chunk.OutgoingCalls.Add(methodSymbol.Name); + Console.WriteLine($"[ПАРСЕР] Успех: {chunk.MethodName} -> {methodSymbol.Name} ({namespaceName})"); + } + } + } + + // 1. Ищем вообще ВСЕ слова (идентификаторы) внутри метода + var identifiers = method.DescendantNodes().OfType(); + + foreach (var identifier in identifiers) + { + // 2. Спрашиваем у семантической модели: "Что это за слово?" + var symbolInfo = semanticModel.GetSymbolInfo(identifier); + + // 3. Если это тип данных (класс, структура, интерфейс или ЕНУМ) + if (symbolInfo.Symbol is INamedTypeSymbol typeSymbol) + { + string namespaceName = typeSymbol.ContainingNamespace?.ToString() ?? ""; + + // Отсеиваем системные типы (string, int, List и т.д.) + if (!namespaceName.StartsWith("System") && !namespaceName.StartsWith("Microsoft")) + { + // Проверяем, что это именно то, что нам нужно + if (typeSymbol.TypeKind == TypeKind.Enum) + { + chunk.OutgoingCalls.Add(typeSymbol.Name); + Console.WriteLine($"[ПАРСЕР] Нашли использование енума -> {typeSymbol.Name}"); + } + else if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Interface) + { + chunk.OutgoingCalls.Add(typeSymbol.Name); + Console.WriteLine($"[ПАРСЕР] Нашли использование класса/интерфейса -> {typeSymbol.Name}"); + } + } + } + } + + // Убираем дубликаты + chunk.OutgoingCalls = chunk.OutgoingCalls.Distinct().ToList(); + chunksList.Add(chunk); + } + } + + return chunksList; + } } } diff --git a/CodeBase/Services/CodeService.cs b/CodeBase/Services/CodeService.cs index 415d34e..3fee434 100644 --- a/CodeBase/Services/CodeService.cs +++ b/CodeBase/Services/CodeService.cs @@ -1,10 +1,9 @@ using CodeBase.Models; -using CodeBase.Warehouse; namespace CodeBase.Services { public class CodeService(ChunkService service, - QdrantRepository repository, + GraphRepository repository, VectorizationService vectorizationService, LlmService llmService) { @@ -15,20 +14,7 @@ namespace CodeBase.Services 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 allChunks = await service.ParseProjectAsync(path); var chunks = await vectorizationService.EnrichChunksWithVectorsAsync(allChunks); @@ -49,7 +35,7 @@ namespace CodeBase.Services } // Главный метод поиска - public async Task> SearchAsync( + public async Task> SearchAsync( float[] queryVector, string name, int topK = 3) // Возвращаем топ-3 результата diff --git a/CodeBase/Services/LlmPromptBuilder.cs b/CodeBase/Services/LlmPromptBuilder.cs index 2321352..ff32ee2 100644 --- a/CodeBase/Services/LlmPromptBuilder.cs +++ b/CodeBase/Services/LlmPromptBuilder.cs @@ -4,38 +4,47 @@ using System.Text; public class LlmPromptBuilder { - public string BuildPrompt(string userQuestion, List foundChunks) + public string BuildPrompt(string userQuestion, List graphContexts) { - var sb = new StringBuilder(); + var promptBuilder = new StringBuilder(); - // 1. Задаем жесткую роль и ограничения - sb.AppendLine("Ты — опытный разработчик и архитектор. Твоя задача — ответить на вопрос пользователя."); - sb.AppendLine("ОТВЕЧАЙ СТРОГО НА ОСНОВЕ ПРЕДОСТАВЛЕННОГО КОДА НИЖЕ."); - sb.AppendLine("Если в коде нет ответа на вопрос, честно скажи: «В данном фрагменте кода нет этой информации». Не придумывай функции, которых нет в тексте."); + promptBuilder.AppendLine("Ты — опытный C#-архитектор. Твоя задача — ответить на вопрос пользователя, опираясь ИСКЛЮЧИТЕЛЬНО на предоставленный граф вызовов и зависимостей кода. Не придумывай методы или классы, которых нет в контексте."); + promptBuilder.AppendLine("\nКонтекст из кодовой базы (Граф зависимостей):"); - sb.AppendLine("\n================ ПРЕДОСТАВЛЕННЫЙ КОД ================"); - - // 2. Вклеиваем найденные чанки с контекстом - foreach (var chunk in foundChunks) + foreach (var node in graphContexts) { - sb.AppendLine($"Файл: {chunk.FilePath}"); - sb.AppendLine($"Класс: {chunk.ClassName}"); - sb.AppendLine($"Метод: {chunk.MethodName}"); - if (!string.IsNullOrEmpty(chunk.Documentation)) + promptBuilder.AppendLine("--------------------------------------------------"); + promptBuilder.AppendLine($"[ГЛАВНЫЙ УЗЕЛ]"); + promptBuilder.AppendLine($"Имя: {node.MethodName}"); + promptBuilder.AppendLine($"Файл: {node.FilePath}"); + + // Добавляем зависимости ВНИЗ (что использует метод) + if (node.OutgoingDependencies.Any()) { - sb.AppendLine($"Документация: {chunk.Documentation}"); + promptBuilder.AppendLine("\n[ИСПОЛЬЗУЕТ ВНУТРИ СЕБЯ]:"); + foreach (var dep in node.OutgoingDependencies.Distinct()) + { + promptBuilder.AppendLine($"- {dep}"); + } } - sb.AppendLine("Код:"); - sb.AppendLine("```csharp"); - sb.AppendLine(chunk.Content); - sb.AppendLine("```"); - sb.AppendLine("--------------------------------------------------"); + + // Добавляем зависимости ВВЕРХ (кто зависит от метода) + if (node.IncomingDependencies.Any()) + { + promptBuilder.AppendLine("\n[ВЫЗЫВАЕТСЯ ИЗ]:"); + foreach (var caller in node.IncomingDependencies.Distinct()) + { + promptBuilder.AppendLine($"- {caller}"); + } + } + + promptBuilder.AppendLine("\nКод:"); + promptBuilder.AppendLine(node.Content); } - // 3. Добавляем сам вопрос - sb.AppendLine("\n================ ВОПРОС ПОЛЬЗОВАТЕЛЯ ================"); - sb.AppendLine(userQuestion); + promptBuilder.AppendLine("--------------------------------------------------"); + promptBuilder.AppendLine($"Вопрос пользователя: {userQuestion}"); - return sb.ToString(); + return promptBuilder.ToString(); } } \ No newline at end of file diff --git a/CodeBase/Services/LlmService.cs b/CodeBase/Services/LlmService.cs index ec3e797..4cf4540 100644 --- a/CodeBase/Services/LlmService.cs +++ b/CodeBase/Services/LlmService.cs @@ -30,11 +30,11 @@ public class LlmService _builder = llmPromptBuilder; } - public async Task AskQuestionAsync(string userQuestion, List foundChunks) + public async Task AskQuestionAsync(string userQuestion, List grafContext) { try { - var finalPrompt = _builder.BuildPrompt(userQuestion, foundChunks); + var finalPrompt = _builder.BuildPrompt(userQuestion, grafContext); // Формируем сообщение. Поскольку мы уже зашили роль и инструкции // внутрь finalPrompt с помощью LlmPromptBuilder, // передаем все это как UserMessage. diff --git a/CodeBase/Warehouse/ChunkWarehouse.cs b/CodeBase/Warehouse/ChunkWarehouse.cs deleted file mode 100644 index 9d1e150..0000000 --- a/CodeBase/Warehouse/ChunkWarehouse.cs +++ /dev/null @@ -1,42 +0,0 @@ -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/PythonProject/Dockerfile b/PythonProject/Dockerfile new file mode 100644 index 0000000..c827a2a --- /dev/null +++ b/PythonProject/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.10-slim + +WORKDIR /app + +# Установка зависимостей +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Копирование кода приложения +COPY . . + +# Команда запуска (замени main:app или script.py на свой стартовый файл) +CMD ["python", "main.py"] \ No newline at end of file diff --git a/README.md b/README.md index 8a4d146..1372133 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ + # CodeBase -Прототип базы знаний по коду проектов. Парсит проект на чанки согласно синтаксическому дереву, векторизует через GrafCodeBert, потом может ответить на вопрос по коду, сравнивая вектора вопроса и чанков, ответ формируется с помощью LLM. + +Прототип базы знаний по коду проектов. На вход получает полный адрес .cproj файла, парсит код для составления графа с векторным представлением каждого метода. Способен ответить на вопрос по проекту, ища векторное совпадение по косинусному сходству и по взаимосвязям в графе + + Patch Notes: -v1: реализовано MVP, работаем только с шарпом, храним все в оперативе. В качестве LLM - qwen2.5-coder:7 \ No newline at end of file + +* v1: реализовано MVP, работаем только с шарпом, храним все в оперативе. В качестве LLM - qwen2.5-coder:7 +* v2: + * Переезд на графовую базу данных Neo4j с автоматической инициализацией. + * С помощью Microsoft.CodeAnalysis.MSBuild парсит все зависимости, исключая попадание системных вызовов в дерево вызовов кода. + * Изменен промт: теперь анализируем не просто чанки, а дерево зависимостей + * Вся инфраструктура теперь в докере: база, питоновский скрипт для векторизации, llm + * НУЖЕН РЕФАКТОРИНГ + * НУЖЕН ПЕРЕСМОТР СКАЧИВАНИЯ LLM Внутри Ollama - долго + * Нужна проверка бага: парсинг сохраняется не сразу \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..dc916a0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +version: '3.8' + +services: + neo4j: + image: neo4j:5.15.0 + container_name: graphrag_neo4j + ports: + - "7474:7474" + - "7687:7687" + environment: + - NEO4J_AUTH=neo4j/password123 + + python-service: + build: ./PythonProject + container_name: graphrag_python + ports: + - "8000:8000" + + ollama: + image: ollama/ollama:latest + container_name: graphrag_ollama + ports: + - "11434:11434" + volumes: + - ollama_data:/root/.ollama + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] + + ollama-init: + image: ollama/ollama:latest + container_name: graphrag_ollama_init + depends_on: + - ollama # Ждем, пока запустится основной сервер + environment: + # Указываем этому контейнеру стучаться в основной сервер + - OLLAMA_HOST=ollama:11434 + # Команда на скачивание (замени qwen2.5-coder на нужную, если передумаешь) + command: pull qwen2.5-coder:7b + +volumes: + ollama_data: \ No newline at end of file