Добавьте файлы проекта.

This commit is contained in:
2026-07-22 20:47:51 +04:00
parent 8050e9194d
commit 17a64809cb
22 changed files with 606 additions and 0 deletions

View File

@@ -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<CodeChunk> ChunkCSharpFile(string filePath, string fileContent)
{
var chunks = new List<CodeChunk>();
var syntaxTree = CSharpSyntaxTree.ParseText(fileContent);
var root = syntaxTree.GetRoot();
// 1. Собираем методы (как и раньше)
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
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<EnumDeclarationSyntax>();
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<ClassDeclarationSyntax>();
foreach (var classSyntax in classes)
{
// Берем только свойства, чтобы понимать структуру модели
var properties = classSyntax.Members.OfType<PropertyDeclarationSyntax>();
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;
}
}
}

View File

@@ -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<List<CodeChunk>> GetCodeChunksAsync(string path, string name)
{
if (!Path.Exists(path))
{
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 chunks = await vectorizationService.EnrichChunksWithVectorsAsync(allChunks);
warehouse.UpsertDictionary(name, allChunks);
return chunks;
}
public async Task<string> 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)));
}
}
}

View File

@@ -0,0 +1,41 @@
using CodeBase.Models;
using System.Collections.Generic;
using System.Text;
public class LlmPromptBuilder
{
public string BuildPrompt(string userQuestion, List<CodeChunk> 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();
}
}

View File

@@ -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<String> AskQuestionAsync(string userQuestion, List<CodeChunk> 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}";
}
}
}

View File

@@ -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<float[]> 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<VectorizeResponse>();
return result?.vector;
}
Console.WriteLine($"[-] Ошибка API при векторизации вопроса: {response.StatusCode}");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"[-] Ошибка подключения к Python-сервису: {ex.Message}");
return null;
}
}
public async Task<List<CodeChunk>> EnrichChunksWithVectorsAsync(List<CodeChunk> 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<VectorizeResponse>();
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;
}
}
}