Добавлена интеграция с Qdrant для поиска по векторам
This commit is contained in:
@@ -4,6 +4,7 @@ using LazyBear.MCP.Services.Jira;
|
|||||||
using LazyBear.MCP.Services.Kubernetes;
|
using LazyBear.MCP.Services.Kubernetes;
|
||||||
using LazyBear.MCP.Services.Logging;
|
using LazyBear.MCP.Services.Logging;
|
||||||
using LazyBear.MCP.Services.Mcp;
|
using LazyBear.MCP.Services.Mcp;
|
||||||
|
using LazyBear.MCP.Services.Qdrant;
|
||||||
using LazyBear.MCP.Services.ToolRegistry;
|
using LazyBear.MCP.Services.ToolRegistry;
|
||||||
using LazyBear.MCP.TUI;
|
using LazyBear.MCP.TUI;
|
||||||
using LazyBear.MCP.TUI.Components;
|
using LazyBear.MCP.TUI.Components;
|
||||||
@@ -27,12 +28,14 @@ var host = Host.CreateDefaultBuilder(args)
|
|||||||
services.AddSingleton<JiraClientProvider>();
|
services.AddSingleton<JiraClientProvider>();
|
||||||
services.AddSingleton<ConfluenceClientProvider>();
|
services.AddSingleton<ConfluenceClientProvider>();
|
||||||
services.AddSingleton<GitLabClientProvider>();
|
services.AddSingleton<GitLabClientProvider>();
|
||||||
|
services.AddSingleton<QdrantClientProvider>();
|
||||||
|
|
||||||
// Модули инструментов (добавь новый IToolModule — он появится в TUI)
|
// Модули инструментов (добавь новый IToolModule — он появится в TUI)
|
||||||
services.AddSingleton<IToolModule, JiraToolModule>();
|
services.AddSingleton<IToolModule, JiraToolModule>();
|
||||||
services.AddSingleton<IToolModule, KubernetesToolModule>();
|
services.AddSingleton<IToolModule, KubernetesToolModule>();
|
||||||
services.AddSingleton<IToolModule, ConfluenceToolModule>();
|
services.AddSingleton<IToolModule, ConfluenceToolModule>();
|
||||||
services.AddSingleton<IToolModule, GitLabToolModule>();
|
services.AddSingleton<IToolModule, GitLabToolModule>();
|
||||||
|
services.AddSingleton<IToolModule, QdrantToolModule>();
|
||||||
|
|
||||||
// HTTP MCP endpoint запускаем в фоне, чтобы TUI оставался владельцем консоли
|
// HTTP MCP endpoint запускаем в фоне, чтобы TUI оставался владельцем консоли
|
||||||
services.AddHostedService<McpWebHostedService>();
|
services.AddHostedService<McpWebHostedService>();
|
||||||
|
|||||||
29
LazyBear.MCP/Services/Qdrant/QdrantClientProvider.cs
Normal file
29
LazyBear.MCP/Services/Qdrant/QdrantClientProvider.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace LazyBear.MCP.Services.Qdrant;
|
||||||
|
|
||||||
|
public sealed class QdrantClientProvider
|
||||||
|
{
|
||||||
|
public RestClient? Client { get; }
|
||||||
|
|
||||||
|
public string? InitializationError { get; }
|
||||||
|
|
||||||
|
public QdrantClientProvider(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = configuration["Qdrant:Url"];
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Qdrant:Url не настроен в конфигурации.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Client = new RestClient(url);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
InitializationError = $"{ex.GetType().Name}: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
333
LazyBear.MCP/Services/Qdrant/QdrantKnowledgeTools.cs
Normal file
333
LazyBear.MCP/Services/Qdrant/QdrantKnowledgeTools.cs
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text.Json;
|
||||||
|
using LazyBear.MCP.Services.ToolRegistry;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace LazyBear.MCP.Services.Qdrant;
|
||||||
|
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class QdrantKnowledgeTools(
|
||||||
|
QdrantClientProvider provider,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ToolRegistryService registry)
|
||||||
|
{
|
||||||
|
private readonly RestClient? _client = provider.Client;
|
||||||
|
private readonly string? _clientInitializationError = provider.InitializationError;
|
||||||
|
private readonly string _apiKey = configuration["Qdrant:ApiKey"] ?? string.Empty;
|
||||||
|
private readonly string _defaultCollection = configuration["Qdrant:DefaultCollection"] ?? "knowledge";
|
||||||
|
|
||||||
|
private const string ModuleName = "Qdrant";
|
||||||
|
|
||||||
|
private bool TryCheckEnabled(string toolName, out string error)
|
||||||
|
{
|
||||||
|
if (!registry.IsToolEnabled(ModuleName, toolName))
|
||||||
|
{
|
||||||
|
error = $"Инструмент '{toolName}' модуля Qdrant отключён в TUI.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Получить список коллекций Qdrant")]
|
||||||
|
public async Task<string> ListCollections(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TryCheckEnabled("ListCollections", out var enabledError)) return enabledError;
|
||||||
|
if (!TryGetClient(out var client, out var error)) return error;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = CreateRequest("/collections");
|
||||||
|
var response = await client.ExecuteAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
if (!response.IsSuccessful || string.IsNullOrWhiteSpace(response.Content))
|
||||||
|
{
|
||||||
|
return FormatResponseError("list_collections", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var document = JsonDocument.Parse(response.Content);
|
||||||
|
if (!document.RootElement.TryGetProperty("result", out var resultElement) ||
|
||||||
|
!resultElement.TryGetProperty("collections", out var collectionsElement) ||
|
||||||
|
collectionsElement.GetArrayLength() == 0)
|
||||||
|
{
|
||||||
|
return "Коллекции Qdrant не найдены.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var names = collectionsElement
|
||||||
|
.EnumerateArray()
|
||||||
|
.Select(item => GetNestedString(item, "name") ?? "unknown")
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return $"Коллекции Qdrant ({names.Length} шт.): {string.Join(", ", names)}";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return FormatException("list_collections", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Создать коллекцию Qdrant для базы знаний")]
|
||||||
|
public async Task<string> CreateCollection(
|
||||||
|
[Description("Имя коллекции. Если пусто — используется Qdrant:DefaultCollection")] string? collection = null,
|
||||||
|
[Description("Размер вектора") ] int vectorSize = 1536,
|
||||||
|
[Description("Метрика расстояния: Cosine, Euclid, Dot или Manhattan")] string distance = "Cosine",
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TryCheckEnabled("CreateCollection", out var enabledError)) return enabledError;
|
||||||
|
if (!TryGetClient(out var client, out var error)) return error;
|
||||||
|
|
||||||
|
var resolvedCollection = ResolveCollection(collection);
|
||||||
|
if (vectorSize <= 0)
|
||||||
|
return "vectorSize должен быть больше 0.";
|
||||||
|
|
||||||
|
if (distance is not ("Cosine" or "Euclid" or "Dot" or "Manhattan"))
|
||||||
|
return $"distance должен быть одним из: Cosine, Euclid, Dot, Manhattan. Получено: '{distance}'.";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = CreateRequest($"/collections/{resolvedCollection}", Method.Put);
|
||||||
|
request.AddJsonBody(new
|
||||||
|
{
|
||||||
|
vectors = new
|
||||||
|
{
|
||||||
|
size = vectorSize,
|
||||||
|
distance
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await client.ExecuteAsync(request, cancellationToken);
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
return FormatResponseError("create_collection", response, resolvedCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Коллекция Qdrant '{resolvedCollection}' создана (vectorSize={vectorSize}, distance={distance}).";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return FormatException("create_collection", ex, resolvedCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Добавить или обновить документ в базе знаний Qdrant")]
|
||||||
|
public async Task<string> UpsertKnowledgeDocument(
|
||||||
|
[Description("ID документа/точки") ] string id,
|
||||||
|
[Description("Вектор embedding") ] float[] vector,
|
||||||
|
[Description("Текст/контент документа") ] string content,
|
||||||
|
[Description("Имя коллекции. Если пусто — используется Qdrant:DefaultCollection")] string? collection = null,
|
||||||
|
[Description("Дополнительные метаданные JSON-объектом") ] Dictionary<string, object?>? metadata = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TryCheckEnabled("UpsertKnowledgeDocument", out var enabledError)) return enabledError;
|
||||||
|
if (!TryGetClient(out var client, out var error)) return error;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(id)) return "id документа не задан.";
|
||||||
|
if (!Guid.TryParse(id, out _) && !ulong.TryParse(id, out _))
|
||||||
|
return $"id должен быть UUID (например, 550e8400-e29b-41d4-a716-446655440000) или uint64. Получено: '{id}'.";
|
||||||
|
if (vector is null || vector.Length == 0) return "vector не задан.";
|
||||||
|
|
||||||
|
var resolvedCollection = ResolveCollection(collection);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var payload = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["content"] = content,
|
||||||
|
["updatedAt"] = DateTimeOffset.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
if (metadata is not null)
|
||||||
|
{
|
||||||
|
foreach (var (key, value) in metadata)
|
||||||
|
{
|
||||||
|
payload[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = CreateRequest($"/collections/{resolvedCollection}/points", Method.Put);
|
||||||
|
request.AddJsonBody(new
|
||||||
|
{
|
||||||
|
points = new object[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
vector,
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await client.ExecuteAsync(request, cancellationToken);
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
return FormatResponseError("upsert_knowledge_document", response, resolvedCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Документ '{id}' сохранён в коллекции '{resolvedCollection}'.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return FormatException("upsert_knowledge_document", ex, resolvedCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Векторный поиск по базе знаний Qdrant")]
|
||||||
|
public async Task<string> SearchKnowledge(
|
||||||
|
[Description("Вектор запроса") ] float[] queryVector,
|
||||||
|
[Description("Имя коллекции. Если пусто — используется Qdrant:DefaultCollection")] string? collection = null,
|
||||||
|
[Description("Количество результатов") ] int limit = 5,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TryCheckEnabled("SearchKnowledge", out var enabledError)) return enabledError;
|
||||||
|
if (!TryGetClient(out var client, out var error)) return error;
|
||||||
|
|
||||||
|
if (queryVector is null || queryVector.Length == 0) return "queryVector не задан.";
|
||||||
|
|
||||||
|
var resolvedCollection = ResolveCollection(collection);
|
||||||
|
var resolvedLimit = Math.Max(1, limit);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = CreateRequest($"/collections/{resolvedCollection}/points/query", Method.Post);
|
||||||
|
request.AddJsonBody(new
|
||||||
|
{
|
||||||
|
query = queryVector,
|
||||||
|
limit = resolvedLimit,
|
||||||
|
with_payload = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await client.ExecuteAsync(request, cancellationToken);
|
||||||
|
if (!response.IsSuccessful || string.IsNullOrWhiteSpace(response.Content))
|
||||||
|
{
|
||||||
|
return FormatResponseError("search_knowledge", response, resolvedCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var document = JsonDocument.Parse(response.Content);
|
||||||
|
if (!document.RootElement.TryGetProperty("result", out var resultsElement) ||
|
||||||
|
resultsElement.GetArrayLength() == 0)
|
||||||
|
{
|
||||||
|
return "Поиск по базе знаний не дал результатов.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = new List<string>();
|
||||||
|
foreach (var item in resultsElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
var pointId = item.TryGetProperty("id", out var idElement) ? idElement.ToString() : "-";
|
||||||
|
var score = item.TryGetProperty("score", out var scoreElement)
|
||||||
|
? scoreElement.GetDouble().ToString("0.####")
|
||||||
|
: "-";
|
||||||
|
var content = (GetNestedString(item, "payload", "content") ?? "(без контента)")
|
||||||
|
.ReplaceLineEndings(" ");
|
||||||
|
lines.Add($"id={pointId}; score={score}; content={content}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Результаты поиска Qdrant ({lines.Count}):\n{string.Join('\n', lines)}";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return FormatException("search_knowledge", ex, resolvedCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Удалить документ из базы знаний Qdrant по ID")]
|
||||||
|
public async Task<string> DeleteKnowledgeDocument(
|
||||||
|
[Description("ID документа/точки") ] string id,
|
||||||
|
[Description("Имя коллекции. Если пусто — используется Qdrant:DefaultCollection")] string? collection = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TryCheckEnabled("DeleteKnowledgeDocument", out var enabledError)) return enabledError;
|
||||||
|
if (!TryGetClient(out var client, out var error)) return error;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(id)) return "id документа не задан.";
|
||||||
|
if (!Guid.TryParse(id, out _) && !ulong.TryParse(id, out _))
|
||||||
|
return $"id должен быть UUID (например, 550e8400-e29b-41d4-a716-446655440000) или uint64. Получено: '{id}'.";
|
||||||
|
|
||||||
|
var resolvedCollection = ResolveCollection(collection);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = CreateRequest($"/collections/{resolvedCollection}/points/delete", Method.Post);
|
||||||
|
request.AddJsonBody(new
|
||||||
|
{
|
||||||
|
points = new[] { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await client.ExecuteAsync(request, cancellationToken);
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
return FormatResponseError("delete_knowledge_document", response, resolvedCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Документ '{id}' удалён из коллекции '{resolvedCollection}'.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return FormatException("delete_knowledge_document", ex, resolvedCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ResolveCollection(string? collection)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(collection) ? _defaultCollection : collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestRequest CreateRequest(string resource, Method method = Method.Get)
|
||||||
|
{
|
||||||
|
var request = new RestRequest(resource, method);
|
||||||
|
request.AddHeader("Accept", "application/json");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_apiKey))
|
||||||
|
{
|
||||||
|
request.AddHeader("api-key", _apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetClient(out RestClient client, out string error)
|
||||||
|
{
|
||||||
|
if (_client is null)
|
||||||
|
{
|
||||||
|
client = null!;
|
||||||
|
var details = string.IsNullOrWhiteSpace(_clientInitializationError)
|
||||||
|
? string.Empty
|
||||||
|
: $" Детали: {_clientInitializationError}";
|
||||||
|
error = "Qdrant клиент не инициализирован. Проверьте Qdrant:Url." + details;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = _client;
|
||||||
|
error = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetNestedString(JsonElement element, params string[] path)
|
||||||
|
{
|
||||||
|
var current = element;
|
||||||
|
foreach (var segment in path)
|
||||||
|
{
|
||||||
|
if (!current.TryGetProperty(segment, out current))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return current.ValueKind == JsonValueKind.String ? current.GetString() : current.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatResponseError(string toolName, RestResponse response, string? resource = null)
|
||||||
|
{
|
||||||
|
var resourcePart = string.IsNullOrWhiteSpace(resource) ? string.Empty : $", resource='{resource}'";
|
||||||
|
var body = string.IsNullOrWhiteSpace(response.Content) ? "-" : response.Content;
|
||||||
|
return $"Ошибка Qdrant в tool '{toolName}'{resourcePart}: status={(int)response.StatusCode}, details={body}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatException(string toolName, Exception exception, string? resource = null)
|
||||||
|
{
|
||||||
|
var resourcePart = string.IsNullOrWhiteSpace(resource) ? string.Empty : $", resource='{resource}'";
|
||||||
|
return $"Ошибка Qdrant в tool '{toolName}'{resourcePart}: {exception.GetType().Name}: {exception.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
18
LazyBear.MCP/Services/Qdrant/QdrantToolModule.cs
Normal file
18
LazyBear.MCP/Services/Qdrant/QdrantToolModule.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using LazyBear.MCP.Services.ToolRegistry;
|
||||||
|
|
||||||
|
namespace LazyBear.MCP.Services.Qdrant;
|
||||||
|
|
||||||
|
public sealed class QdrantToolModule : IToolModule
|
||||||
|
{
|
||||||
|
public string ModuleName => "Qdrant";
|
||||||
|
public string Description => "Qdrant: база знаний (коллекции, документы, векторный поиск)";
|
||||||
|
|
||||||
|
public IReadOnlyList<string> ToolNames =>
|
||||||
|
[
|
||||||
|
"ListCollections",
|
||||||
|
"CreateCollection",
|
||||||
|
"UpsertKnowledgeDocument",
|
||||||
|
"SearchKnowledge",
|
||||||
|
"DeleteKnowledgeDocument"
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -19,6 +19,11 @@
|
|||||||
"Token": "",
|
"Token": "",
|
||||||
"Project": ""
|
"Project": ""
|
||||||
},
|
},
|
||||||
|
"Qdrant": {
|
||||||
|
"Url": "",
|
||||||
|
"ApiKey": "",
|
||||||
|
"DefaultCollection": "knowledge"
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
1. ✅ Создать Memory Bank структуру
|
1. ✅ Создать Memory Bank структуру
|
||||||
2. 🔄 Продолжить документирование архитектуры и паттернов
|
2. 🔄 Продолжить документирование архитектуры и паттернов
|
||||||
3. ⏳ Обновлять Memory Bank после значимых изменений
|
3. ⏳ Обновлять Memory Bank после значимых изменений
|
||||||
|
4. ⏳ Добавить Qdrant в Memory Bank
|
||||||
|
|
||||||
## 🔍 Важные решения
|
## 🔍 Важные решения
|
||||||
|
|
||||||
@@ -61,6 +62,11 @@
|
|||||||
"Token": "",
|
"Token": "",
|
||||||
"Username": "",
|
"Username": "",
|
||||||
"SpaceKey": ""
|
"SpaceKey": ""
|
||||||
|
},
|
||||||
|
"Qdrant": {
|
||||||
|
"Url": "", // URL Qdrant сервера
|
||||||
|
"ApiKey": "", // Опционально для авторизации
|
||||||
|
"DefaultCollection": "knowledge" // Default коллекция для векторного поиска
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -125,6 +125,22 @@ TUI показывает:
|
|||||||
- Статус подов
|
- Статус подов
|
||||||
- Последние events
|
- Последние events
|
||||||
- Кнопки действий
|
- Кнопки действий
|
||||||
|
- Векторные индексы Qdrant (если используется)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сценарий 4: Поиск знаний через Qdrant
|
||||||
|
|
||||||
|
```
|
||||||
|
User (via AI): "Как настроить деплой nginx?"
|
||||||
|
↓
|
||||||
|
AI вызывает: qdrantKnowledgeTools/search({
|
||||||
|
query: "nginx деплой настройка",
|
||||||
|
vector_embedding: [0.1, 0.2, ...]
|
||||||
|
})
|
||||||
|
↓
|
||||||
|
Qdrant возвращает релевантные документы
|
||||||
|
↓
|
||||||
|
AI предоставляет из Confluence/документации
|
||||||
```
|
```
|
||||||
|
|
||||||
## Метрики успеха
|
## Метрики успеха
|
||||||
@@ -133,6 +149,7 @@ TUI показывает:
|
|||||||
|---------|------|
|
|---------|------|
|
||||||
| Время на задачу (Jira) | -50% после интеграции |
|
| Время на задачу (Jira) | -50% после интеграции |
|
||||||
| Время на задачу (K8s) | -70% после интеграции |
|
| Время на задачу (K8s) | -70% после интеграции |
|
||||||
|
| Время на поиск знаний | -60% с Qdrant |
|
||||||
| Довольство пользователей | >4.5/5 |
|
| Довольство пользователей | >4.5/5 |
|
||||||
| Количество инцидентов | Снизить на -30% |
|
| Количество инцидентов | Снизить на -30% |
|
||||||
|
|
||||||
@@ -163,4 +180,4 @@ TUI показывает:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Документ описывает почему проект существует и как должен работать пользователь.*
|
*Документ описывает почему проект существует и как должен работать пользователь.*
|
||||||
|
|||||||
@@ -50,6 +50,15 @@
|
|||||||
- `create_branch` — создать ветку
|
- `create_branch` — создать ветку
|
||||||
- `delete_branch` — удалить ветку
|
- `delete_branch` — удалить ветку
|
||||||
|
|
||||||
|
### Qdrant Integration (Vector DB)
|
||||||
|
|
||||||
|
- ✅ CRUD коллекции (создание, удаление)
|
||||||
|
- ✅ Добавление/обновление документов (upsert)
|
||||||
|
- ✅ Векторный поиск по коллекции
|
||||||
|
- ✅ Удаление документов по ID
|
||||||
|
- ✅ Поддержка метрик (Cosine, Euclid, Dot)
|
||||||
|
- ✅ Поддержка API ключа (опционально)
|
||||||
|
|
||||||
### MCP Server
|
### MCP Server
|
||||||
|
|
||||||
- ✅ HTTP Transport MCP 1.2.0
|
- ✅ HTTP Transport MCP 1.2.0
|
||||||
@@ -168,7 +177,7 @@
|
|||||||
|
|
||||||
**Состояние**: Development
|
**Состояние**: Development
|
||||||
|
|
||||||
**Последний commit**: `e96bab114ea1a58f3ea7bd5ab40d4645d456cd8f`
|
**Последний commit**: `b5fe2623b3d14333a7138c22456862bff3781b82`
|
||||||
|
|
||||||
**Что работает**: Все основные функциональности готовы
|
**Что работает**: Все основные функциональности готовы
|
||||||
|
|
||||||
|
|||||||
@@ -18,16 +18,17 @@
|
|||||||
↓
|
↓
|
||||||
┌─────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────┐
|
||||||
│ Services Layer (IToolModule) │
|
│ Services Layer (IToolModule) │
|
||||||
│ ┌──────────┐ ┌───────────────┐ ┌────────────────────┐ │
|
│ ┌──────────┐ ┌───────────────┐ ┌────────────────────┐ ┌─────┐│
|
||||||
│ │JiraTools │ │ConfluenceTools│ │KubernetesTools │ │
|
│ │JiraTools │ │ConfluenceTools│ │KubernetesTools │ │Qdrant││
|
||||||
│ └──────────┘ └───────────────┘ └────────────────────┘ │
|
│ └──────────┘ └───────────────┘ └────────────────────┘ └─────┘│
|
||||||
|
│ └────────────────────────────────────────────────────┘ │
|
||||||
└─────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────┘
|
||||||
↓
|
↓
|
||||||
┌─────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────┐
|
||||||
│ External API Layer │
|
│ External API Layer │
|
||||||
│ ┌──────────┐ ┌───────────────┐ ┌────────────────────┐ │
|
│ ┌──────────┐ ┌───────────────┐ ┌────────────────────┐ ┌────┐│
|
||||||
│ │ Jira API │ │Confluence API │ │ K8s API │ │
|
│ │ Jira API │ │Confluence API │ │ K8s API │ │Qdr│ │
|
||||||
│ └──────────┘ └───────────────┘ └────────────────────┘ │
|
│ └──────────┘ └───────────────┘ └────────────────────┘ └───┘ │
|
||||||
└─────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -289,4 +290,33 @@ Console.WriteLine
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Файл описывает систему архитектуры, ключевые компоненты и потоки данных. Обновлять при введении новых архитектурных решений.*
|
### Qdrant Client Provider Pattern
|
||||||
|
|
||||||
|
**File**: `LazyBear.MCP/Services/Qdrant/QdrantClientProvider.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class QdrantClientProvider
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
|
||||||
|
public QdrantClient GetClient()
|
||||||
|
{
|
||||||
|
// Конфиг из appsettings.json
|
||||||
|
var url = _config["Qdrant:Url"];
|
||||||
|
var apiKey = _config["Qdrant:ApiKey"] ?? string.Empty;
|
||||||
|
|
||||||
|
return new QdrantClient(url, apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fallback порядок**:
|
||||||
|
1. Explicit URL из конфига
|
||||||
|
2. Environment variable QDRANT_URL
|
||||||
|
3. Localhost default
|
||||||
|
|
||||||
|
**Ответственность**: Создание клиентов Qdrant с поддержкой API ключа (опционально)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Файл описывает систему архитектуры, ключевые компоненты и потоки данных. Обновлять при введении новых архитектурных решений.*
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
| **Model Context Protocol** | 1.2.0 | MCP стандарт |
|
| **Model Context Protocol** | 1.2.0 | MCP стандарт |
|
||||||
| **Kubernetes Client** | 13+ | .NET SDK для K8s |
|
| **Kubernetes Client** | 13+ | .NET SDK для K8s |
|
||||||
| **RazorConsole** | Latest | TUI framework |
|
| **RazorConsole** | Latest | TUI framework |
|
||||||
|
| **Qdrant.Client** | Latest | Векторный поиск,知识库 |
|
||||||
|
|
||||||
### Файлы конфигурации
|
### Файлы конфигурации
|
||||||
|
|
||||||
@@ -101,6 +102,10 @@ LazyBear.MCP/
|
|||||||
│ │ └── JiraClientProvider.cs
|
│ │ └── JiraClientProvider.cs
|
||||||
│ ├── Confluence/
|
│ ├── Confluence/
|
||||||
│ │ └── ConfluencePagesTools.cs
|
│ │ └── ConfluencePagesTools.cs
|
||||||
|
│ ├── Qdrant/
|
||||||
|
│ │ ├── QdrantClientProvider.cs
|
||||||
|
│ │ ├── QdrantKnowledgeTools.cs
|
||||||
|
│ │ └── QdrantToolModule.cs
|
||||||
│ └── Kubernetes/
|
│ └── Kubernetes/
|
||||||
│ ├── K8sConfigTools.cs
|
│ ├── K8sConfigTools.cs
|
||||||
│ ├── K8sDeploymentTools.cs
|
│ ├── K8sDeploymentTools.cs
|
||||||
@@ -215,6 +220,13 @@ echo '{"jsonrpc":"2.0","id":1,"method":"k8sPodsTools/getPodStatus","params":{"na
|
|||||||
|
|
||||||
- `Jira:Url` обязателен, иначе инициализация провайдера может упасть
|
- `Jira:Url` обязателен, иначе инициализация провайдера может упасть
|
||||||
- `Kubernetes:KubeconfigPath` может быть пустым — используется fallback
|
- `Kubernetes:KubeconfigPath` может быть пустым — используется fallback
|
||||||
|
- `Qdrant:Url` обязателен для векторного поиска, если используется
|
||||||
|
- `Qdrant:ApiKey` опционален, но рекомендуется для безопасного доступа
|
||||||
|
|
||||||
|
### Qdrant Gotchas
|
||||||
|
|
||||||
|
- Косинусная метрика — default для векторного поиска
|
||||||
|
- Размер вектора должен быть фиксированным при создании коллекции
|
||||||
|
|
||||||
### RazorConsole Gotchas
|
### RazorConsole Gotchas
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user