Попытки в мультипарсинг

This commit is contained in:
2026-07-24 13:47:21 +04:00
parent b2a13528ee
commit eb8dac7627
22 changed files with 1486 additions and 37 deletions

View File

@@ -29,7 +29,7 @@ namespace CodeBase.Services
var chunk = new CodeChunk
{
FilePath = filePath,
MethodName = method.Identifier.Text,
EntityName = method.Identifier.Text,
Content = method.ToFullString().Trim()
};
@@ -68,7 +68,7 @@ namespace CodeBase.Services
chunks.Add(new CodeChunk
{
FilePath = filePath,
MethodName = enumSyntax.Identifier.Text, // Сохраняем имя енама
EntityName = enumSyntax.Identifier.Text, // Сохраняем имя енама
Content = enumSyntax.ToFullString().Trim()
});
}
@@ -84,7 +84,7 @@ namespace CodeBase.Services
chunks.Add(new CodeChunk
{
FilePath = filePath,
MethodName = classSyntax.Identifier.Text,
EntityName = classSyntax.Identifier.Text,
// Сохраняем объявление класса и его свойства
Content = $"class {classSyntax.Identifier.Text} {{\n" +
string.Join("\n", properties.Select(p => p.ToFullString().Trim())) +
@@ -119,7 +119,7 @@ namespace CodeBase.Services
{
var chunk = new CodeChunk
{
MethodName = method.Identifier.Text,
EntityName = method.Identifier.Text,
Content = method.ToFullString(),
FilePath = document.FilePath
};
@@ -140,7 +140,7 @@ namespace CodeBase.Services
if (!namespaceName.StartsWith("System") && !namespaceName.StartsWith("Microsoft"))
{
chunk.OutgoingCalls.Add(methodSymbol.Name);
Console.WriteLine($"[ПАРСЕР] Успех: {chunk.MethodName} -> {methodSymbol.Name} ({namespaceName})");
Console.WriteLine($"[ПАРСЕР] Успех: {chunk.EntityName} -> {methodSymbol.Name} ({namespaceName})");
}
}
}
@@ -184,5 +184,7 @@ namespace CodeBase.Services
return chunksList;
}
}
}

View File

@@ -1,9 +1,10 @@
using CodeBase.Models;
using CodeBase.Repositories;
namespace CodeBase.Services
{
public class CodeService(ChunkService service,
GraphRepository repository,
ScipRepository repository,
VectorizationService vectorizationService,
LlmService llmService)
{
@@ -18,7 +19,7 @@ namespace CodeBase.Services
var chunks = await vectorizationService.EnrichChunksWithVectorsAsync(allChunks);
await repository.SaveChunksAsync(name, chunks);
//await repository.SaveChunksAsync(name, chunks);
return chunks;
}
@@ -35,12 +36,12 @@ namespace CodeBase.Services
}
// Главный метод поиска
public async Task<List<GraphNodeContext>> SearchAsync(
public async Task<List<RetrievedContext>> SearchAsync(
float[] queryVector,
string name,
int topK = 3) // Возвращаем топ-3 результата
{
var results = await repository.SearchAsync(name, queryVector, topK);
var results = await repository.FindSimilarNodesAsync(queryVector, name, topK);
return results;
}

View File

@@ -4,22 +4,24 @@ using System.Text;
public class LlmPromptBuilder
{
public string BuildPrompt(string userQuestion, List<GraphNodeContext> graphContexts)
public string BuildPrompt(string userQuestion, List<RetrievedContext> graphContexts)
{
var promptBuilder = new StringBuilder();
promptBuilder.AppendLine("Ты — опытный C#-архитектор. Твоя задача — ответить на вопрос пользователя, опираясь ИСКЛЮЧИТЕЛЬНО на предоставленный граф вызовов и зависимостей кода. Не придумывай методы или классы, которых нет в контексте.");
// Универсальная роль архитектора без привязки к конкретному языку
promptBuilder.AppendLine("Ты — опытный ИТ-архитектор и разработчик. Твоя задача — ответить на вопрос пользователя, опираясь ИСКЛЮЧИТЕЛЬНО на предоставленный граф вызовов и зависимостей кода. Не придумывай методы или классы, которых нет в контексте.");
promptBuilder.AppendLine("\nКонтекст из кодовой базы (Граф зависимостей):");
foreach (var node in graphContexts)
{
promptBuilder.AppendLine("--------------------------------------------------");
promptBuilder.AppendLine($"[ГЛАВНЫЙ УЗЕЛ]");
promptBuilder.AppendLine($"Имя: {node.MethodName}");
promptBuilder.AppendLine($"[УЗЕЛ ГРАФА]");
promptBuilder.AppendLine($"Проект: {node.ProjectName}");
promptBuilder.AppendLine($"Имя сущности: {node.EntityName}");
promptBuilder.AppendLine($"Файл: {node.FilePath}");
// Добавляем зависимости ВНИЗ (что использует метод)
if (node.OutgoingDependencies.Any())
if (node.OutgoingDependencies != null && node.OutgoingDependencies.Any())
{
promptBuilder.AppendLine("\n[ИСПОЛЬЗУЕТ ВНУТРИ СЕБЯ]:");
foreach (var dep in node.OutgoingDependencies.Distinct())
@@ -29,7 +31,7 @@ public class LlmPromptBuilder
}
// Добавляем зависимости ВВЕРХ (кто зависит от метода)
if (node.IncomingDependencies.Any())
if (node.IncomingDependencies != null && node.IncomingDependencies.Any())
{
promptBuilder.AppendLine("\n[ВЫЗЫВАЕТСЯ ИЗ]:");
foreach (var caller in node.IncomingDependencies.Distinct())

View File

@@ -4,6 +4,7 @@ using OpenAI.Chat;
using System;
using System.ClientModel;
using System.Threading.Tasks;
using System.Xml.Linq;
public class LlmService
{
@@ -30,7 +31,7 @@ public class LlmService
_builder = llmPromptBuilder;
}
public async Task<String> AskQuestionAsync(string userQuestion, List<GraphNodeContext> grafContext)
public async Task<String> AskQuestionAsync(string userQuestion, List<RetrievedContext> grafContext)
{
try
{

View File

@@ -0,0 +1,75 @@
using CodeBase.Models;
using CodeBase.Orchestrators;
using CodeBase.Services;
using Google.Protobuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace CodeBase.Parsers
{
public class ScipParser(ScipOrchestrator scipOrchestrator)
{
public async Task<List<CodeChunk>> ParseProjectAsync(string projectRootPath, string language, string name)
{
var scipFilePath = Path.Combine(projectRootPath, "index.scip");
if (!File.Exists(scipFilePath))
{
await scipOrchestrator.GenerateScipAsync(projectRootPath, language);
}
var filter = new UniversalFileFilter();
using var stream = File.OpenRead(scipFilePath);
var scipIndex = Scip.Index.Parser.ParseFrom(stream);
var extractedChunks = new List<CodeChunk>();
foreach (var document in scipIndex.Documents)
{
if (!filter.IsValidCodeFile(document.RelativePath)) continue;
var absoluteFilePath = Path.Combine(projectRootPath, document.RelativePath);
if (!File.Exists(absoluteFilePath)) continue;
var fileLines = await File.ReadAllLinesAsync(absoluteFilePath);
var declarations = document.Occurrences.Where(o => (o.SymbolRoles & 1) == 1);
foreach (var occ in declarations)
{
int startLine = occ.Range[0];
int endLine = occ.Range.Count == 3 ? occ.Range[0] : occ.Range[2];
if (startLine < 0 || endLine >= fileLines.Length) continue;
var codeSnippet = string.Join(
Environment.NewLine,
fileLines.Skip(startLine).Take(endLine - startLine + 1)
);
// Возвращаем чанки пока БЕЗ векторов
extractedChunks.Add(new CodeChunk
{
Id = occ.Symbol,
EntityName = ExtractSimpleName(occ.Symbol),
FilePath = document.RelativePath,
Content = codeSnippet,
Language = document.Language,
ProjectName = name
});
}
}
return extractedChunks;
}
private string ExtractSimpleName(string scipSymbol)
{
if (string.IsNullOrEmpty(scipSymbol)) return "Unknown";
var parts = scipSymbol.Split(new[] { '#', '.', '(', ')' }, StringSplitOptions.RemoveEmptyEntries);
return parts.LastOrDefault() ?? scipSymbol;
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using CodeBase.Parsers;
using CodeBase.Repositories;
namespace CodeBase.Services
{
public class ScipProcessingService
{
private readonly ScipParser _parser;
private readonly VectorizationService _vectorizationService;
private readonly ScipRepository _repository;
// Внедрение зависимостей
public ScipProcessingService(
ScipParser parser,
VectorizationService vectorizationService,
ScipRepository repository)
{
_parser = parser;
_vectorizationService = vectorizationService;
_repository = repository;
}
public async Task ProcessAndSaveProjectAsync(string projectRootPath, string lang, string name)
{
try
{
Console.WriteLine($"[СЕРВИС] Запуск парсинга проекта: {projectRootPath}");
// 1. Получаем "сырые" чанки из парсера
var chunks = await _parser.ParseProjectAsync(projectRootPath, lang, name);
Console.WriteLine($"[СЕРВИС] Найдено {chunks.Count} сущностей. Начинаем векторизацию...");
// 2. Обогащаем каждый чанк векторным представлением GraphCodeBERT
chunks = await _vectorizationService.EnrichChunksWithVectorsAsync(chunks);
// 3. Сохраняем готовую сборку в графовую базу
await _repository.SaveChunksAsync(chunks);
Console.WriteLine("[СЕРВИС] Проект успешно обработан и сохранен в БД.");
}
catch (Exception ex)
{
Console.WriteLine($"[СЕРВИС ОШИБКА] Сбой при обработке проекта: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,52 @@
namespace CodeBase.Services
{
public class UniversalFileFilter
{
// 1. Оставляем только те языки, которые нам реально интересны
private readonly HashSet<string> _allowedExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".cs", ".py", ".go", ".ts", ".js", ".java", ".cpp", ".c", ".h", ".json"
};
// 2. Глобальные папки с мусором и зависимостями (универсально для разных стеков)
private readonly string[] _ignoredDirectories =
{
"/obj/", "\\obj\\",
"/bin/", "\\bin\\",
"/node_modules/", "\\node_modules\\",
"/venv/", "\\venv\\",
"/.env/", "\\.env\\",
"/dist/", "\\dist\\",
"/build/", "\\build\\",
"/.git/", "\\.git\\"
};
// 3. Паттерны автосгенерированных файлов
private readonly string[] _ignoredFileSuffixes =
{
".g.cs",
".designer.cs",
".generated.cs",
"AssemblyInfo.cs"
};
public bool IsValidCodeFile(string relativePath)
{
if (string.IsNullOrWhiteSpace(relativePath)) return false;
// Проверка 1: Расширение файла
var ext = Path.GetExtension(relativePath);
if (!_allowedExtensions.Contains(ext)) return false;
// Проверка 2: Находится ли файл в мусорной папке
if (_ignoredDirectories.Any(dir => relativePath.Contains(dir, StringComparison.OrdinalIgnoreCase)))
return false;
// Проверка 3: Является ли файл автосгенерированным
if (_ignoredFileSuffixes.Any(suffix => relativePath.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)))
return false;
return true;
}
}
}

View File

@@ -22,6 +22,28 @@ namespace CodeBase.Services
// Адрес нашего локального Python-сервиса
_httpClient.BaseAddress = new Uri("http://localhost:8000/");
}
private const int MaxCodeLength = 1500;
private string BuildSafeContext(CodeChunk chunk)
{
// Метаданные (оставляем всегда целиком)
string metadata = $"File: {chunk.FilePath}\nEntity: {chunk.EntityName}\nCode:\n";
// Вычисляем, сколько символов у нас осталось для самого кода
int remainingLength = MaxCodeLength - metadata.Length;
string code = chunk.Content;
// Если код слишком длинный — аккуратно отрезаем хвост
if (code.Length > remainingLength && remainingLength > 0)
{
code = code.Substring(0, remainingLength) + "\n...[TRUNCATED]";
Console.WriteLine($"[ВЕКТОРИЗАЦИЯ] Метод {chunk.EntityName} слишком длинный. Обрезан до {MaxCodeLength} символов.");
}
return metadata + code;
}
public async Task<float[]> GetVectorAsync(string text)
@@ -55,7 +77,7 @@ namespace CodeBase.Services
{
// 1. Склеиваем контекст
// Мы даем нейросети подсказку о том, где именно лежит этот код
string contextText = $"File: {chunk.FilePath}\nClass: {chunk.ClassName}\nMethod: {chunk.MethodName}\nCode:\n{chunk.Content}";
string contextText = BuildSafeContext(chunk);
var requestBody = new VectorizeRequest { text = contextText };
@@ -71,13 +93,13 @@ namespace CodeBase.Services
if (result != null && result.vector != null)
{
// 4. Сохраняем вектор прямо в наш объект в памяти
chunk.Vector = result.vector;
Console.WriteLine($"[+] Векторизован метод: {chunk.MethodName}");
chunk.Embedding = result.vector;
Console.WriteLine($"[+] Векторизована сущность: {chunk.EntityName}");
}
}
else
{
Console.WriteLine($"[-] Ошибка API для {chunk.MethodName}: {response.StatusCode}");
Console.WriteLine($"[-] Ошибка API для {chunk.EntityName}: {response.StatusCode}");
}
}