Переехали на графовую модель, засунули все в докер, поменяли парсинг
This commit is contained in:
@@ -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<MethodDeclarationSyntax>();
|
||||
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<InvocationExpressionSyntax>();
|
||||
|
||||
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<List<CodeChunk>> ParseProjectAsync(string csprojPath)
|
||||
{
|
||||
// 1. Создаем воркспейс и загружаем проект (это может занять пару секунд)
|
||||
using var workspace = MSBuildWorkspace.Create();
|
||||
Console.WriteLine("Загружаем проект и строим семантическую модель...");
|
||||
var project = await workspace.OpenProjectAsync(csprojPath);
|
||||
|
||||
var chunksList = new List<CodeChunk>();
|
||||
|
||||
// 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<MethodDeclarationSyntax>();
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var chunk = new CodeChunk
|
||||
{
|
||||
MethodName = method.Identifier.Text,
|
||||
Content = method.ToFullString(),
|
||||
FilePath = document.FilePath
|
||||
};
|
||||
|
||||
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
||||
|
||||
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<IdentifierNameSyntax>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CodeChunk>();
|
||||
|
||||
// Находим все .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<List<CodeChunk>> SearchAsync(
|
||||
public async Task<List<GraphNodeContext>> SearchAsync(
|
||||
float[] queryVector,
|
||||
string name,
|
||||
int topK = 3) // Возвращаем топ-3 результата
|
||||
|
||||
@@ -4,38 +4,47 @@ using System.Text;
|
||||
|
||||
public class LlmPromptBuilder
|
||||
{
|
||||
public string BuildPrompt(string userQuestion, List<CodeChunk> foundChunks)
|
||||
public string BuildPrompt(string userQuestion, List<GraphNodeContext> 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();
|
||||
}
|
||||
}
|
||||
@@ -30,11 +30,11 @@ public class LlmService
|
||||
_builder = llmPromptBuilder;
|
||||
}
|
||||
|
||||
public async Task<String> AskQuestionAsync(string userQuestion, List<CodeChunk> foundChunks)
|
||||
public async Task<String> AskQuestionAsync(string userQuestion, List<GraphNodeContext> grafContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
var finalPrompt = _builder.BuildPrompt(userQuestion, foundChunks);
|
||||
var finalPrompt = _builder.BuildPrompt(userQuestion, grafContext);
|
||||
// Формируем сообщение. Поскольку мы уже зашили роль и инструкции
|
||||
// внутрь finalPrompt с помощью LlmPromptBuilder,
|
||||
// передаем все это как UserMessage.
|
||||
|
||||
Reference in New Issue
Block a user