Implement Skills feature: filesystem-based skills with agent assignment
- Add SkillService: scans data/skills/ directory, parses _meta.json and SKILL.md frontmatter - Add SkillEndpoint: REST API for listing skills and managing agent-skill assignments - Add agent_skills DB table for agent-to-skill mapping (only relation in DB, skills live on filesystem) - Extend SystemPromptBuilder: inject assigned skills' SKILL.md content into system prompt - Extend ChatEndpoint + SessionEndpoint: load agent skills and pass to prompt builder - Frontend: skill checkboxes in agent form, API functions for skill management - Add form.css styling for skill list items
This commit is contained in:
1 parent
b18dc78651
commit
cf6caea2dc
11 files changed
+454
-6
No files matched your search
@@ -23,7 +23,7 @@ public static class ChatEndpoint
|
||||
app.MapPost("/api/chat", HandleChat);
|
||||
}
|
||||
|
||||
private static async Task HandleChat(HttpRequest req, HttpResponse res, IHttpClientFactory httpFactory, IConfiguration config, ToolService toolService, DatabaseService db, StreamingTracker streamingTracker, MemoryService memoryService, CancellationToken cancellationToken)
|
||||
private static async Task HandleChat(HttpRequest req, HttpResponse res, IHttpClientFactory httpFactory, IConfiguration config, ToolService toolService, DatabaseService db, StreamingTracker streamingTracker, MemoryService memoryService, SkillService skillService, CancellationToken cancellationToken)
|
||||
{
|
||||
var body = await JsonSerializer.DeserializeAsync<ChatRequest>(req.Body);
|
||||
if (body == null || body.Messages == null || body.Messages.Count == 0)
|
||||
@@ -80,7 +80,8 @@ public static class ChatEndpoint
|
||||
var markedMessages = await db.GetMarkedMessages(sessionId);
|
||||
var agentMemoryDir = memoryService.GetAgentMemoryDir(agent.AgentId);
|
||||
var dataDir = memoryService.DataDir;
|
||||
var systemPromptText = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir);
|
||||
var agentSkills = await db.GetAgentSkills(agent.AgentId);
|
||||
var systemPromptText = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, agentSkills, skillService);
|
||||
|
||||
var chainId = Guid.NewGuid().ToString("N");
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public static class SessionEndpoint
|
||||
ctx.Response.StatusCode = 204;
|
||||
}
|
||||
|
||||
private static async Task HandleSystemPrompt(HttpContext ctx, DatabaseService db, MemoryService memoryService)
|
||||
private static async Task HandleSystemPrompt(HttpContext ctx, DatabaseService db, MemoryService memoryService, SkillService skillService)
|
||||
{
|
||||
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
|
||||
if (string.IsNullOrEmpty(sessionId))
|
||||
@@ -125,7 +125,8 @@ public static class SessionEndpoint
|
||||
var markedMessages = await db.GetMarkedMessages(sessionId);
|
||||
var agentMemoryDir = memoryService.GetAgentMemoryDir(agent.AgentId);
|
||||
var dataDir = memoryService.DataDir;
|
||||
var systemPrompt = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir);
|
||||
var agentSkills = await db.GetAgentSkills(agent.AgentId);
|
||||
var systemPrompt = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, agentSkills, skillService);
|
||||
|
||||
ctx.Response.ContentType = "text/plain; charset=utf-8";
|
||||
await ctx.Response.WriteAsync(systemPrompt);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Gott.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Gott.Endpoints;
|
||||
|
||||
public static class SkillEndpoint
|
||||
{
|
||||
public static void MapSkillEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
// Skills (Dateisystem-basiert)
|
||||
app.MapGet("/api/skills", HandleListSkills);
|
||||
|
||||
// Agent ↔ Skill Zuordnung
|
||||
app.MapGet("/api/agents/{agentId}/skills", HandleGetAgentSkills);
|
||||
app.MapPut("/api/agents/{agentId}/skills", HandleSetAgentSkills);
|
||||
app.MapPost("/api/agents/{agentId}/skills/{slug}", HandleAddAgentSkill);
|
||||
app.MapDelete("/api/agents/{agentId}/skills/{slug}", HandleRemoveAgentSkill);
|
||||
}
|
||||
|
||||
private static async Task HandleListSkills(HttpContext ctx, SkillService skillService)
|
||||
{
|
||||
var skills = skillService.GetAllSkills();
|
||||
await ctx.Response.WriteAsJsonAsync(skills);
|
||||
}
|
||||
|
||||
private static async Task HandleGetAgentSkills(HttpContext ctx, DatabaseService db, SkillService skillService)
|
||||
{
|
||||
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
|
||||
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
|
||||
|
||||
var slugs = await db.GetAgentSkills(agentId);
|
||||
var allSkills = skillService.GetAllSkills();
|
||||
|
||||
// Nur Skills zurückgeben die auch wirklich existieren
|
||||
var result = allSkills
|
||||
.Where(s => slugs.Contains(s.Slug))
|
||||
.ToList();
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(result);
|
||||
}
|
||||
|
||||
private static async Task HandleSetAgentSkills(HttpContext ctx, DatabaseService db)
|
||||
{
|
||||
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
|
||||
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
|
||||
|
||||
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
||||
var slugs = body.TryGetProperty("skills", out var arr)
|
||||
? arr.EnumerateArray().Select(e => e.GetString()).Where(s => !string.IsNullOrEmpty(s)).ToList()
|
||||
: new System.Collections.Generic.List<string>();
|
||||
|
||||
await db.SetAgentSkills(agentId, slugs);
|
||||
|
||||
var updated = await db.GetAgentSkills(agentId);
|
||||
await ctx.Response.WriteAsJsonAsync(updated);
|
||||
}
|
||||
|
||||
private static async Task HandleAddAgentSkill(HttpContext ctx, DatabaseService db)
|
||||
{
|
||||
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
|
||||
var slug = ctx.Request.RouteValues["slug"]?.ToString();
|
||||
if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug)) { ctx.Response.StatusCode = 400; return; }
|
||||
|
||||
await db.AddAgentSkill(agentId, slug);
|
||||
ctx.Response.StatusCode = 204;
|
||||
}
|
||||
|
||||
private static async Task HandleRemoveAgentSkill(HttpContext ctx, DatabaseService db)
|
||||
{
|
||||
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
|
||||
var slug = ctx.Request.RouteValues["slug"]?.ToString();
|
||||
if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug)) { ctx.Response.StatusCode = 400; return; }
|
||||
|
||||
await db.RemoveAgentSkill(agentId, slug);
|
||||
ctx.Response.StatusCode = 204;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user