diff options
Diffstat (limited to 'src/Agent.Core/Runtime/AgentRuntime.cs')
| -rw-r--r-- | src/Agent.Core/Runtime/AgentRuntime.cs | 67 |
1 files changed, 66 insertions, 1 deletions
diff --git a/src/Agent.Core/Runtime/AgentRuntime.cs b/src/Agent.Core/Runtime/AgentRuntime.cs index 228ac41..f6bb483 100644 --- a/src/Agent.Core/Runtime/AgentRuntime.cs +++ b/src/Agent.Core/Runtime/AgentRuntime.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Agent.Core.Ollama; using Agent.Core.Prompts; using Agent.Core.Sessions; @@ -28,12 +29,16 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag await _tools.BeginTaskAsync(taskId, cancellationToken).ConfigureAwait(false); if (task.Images.Count > 0 && await ollama.SupportsVisionAsync(cancellationToken).ConfigureAwait(false) is false) throw new InvalidOperationException(settings.VisionUnsupportedMessage); + + // Progressive Disclosure Stage 2: LLM Skill Selection. + IReadOnlyList<AgentSkill> selectedSkills = await SelectSkillsAsync(task.Request, cancellationToken).ConfigureAwait(false); + var messages = PromptComposer.Compose(new PromptContext( FixedPrompt.Load(), settings.UserSystemPrompt, settings.Locale, settings.Skills, - [], // SelectedSkills: populated after LLM skill selection (Progressive Disclosure Stage 2→3). + selectedSkills, // Stage 3: selected skill instructions injected by PromptComposer. settings.SandboxCapabilitySummary, "Current task: " + task.Request, string.Empty, @@ -134,6 +139,66 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag } } + private async Task<IReadOnlyList<AgentSkill>> SelectSkillsAsync(string request, CancellationToken cancellationToken) + { + if (settings.Skills.Count == 0) return []; + + // Build a minimal prompt with skill catalog for the LLM to choose from. + string catalog = string.Join("\n", settings.Skills.Select(static s => $"- {s.Name}: {s.Description}")); + var selectionMessages = new List<OllamaMessage> + { + new("system", $"Select skills relevant to the user request. Available skills:\n{catalog}\n\nReturn a JSON object with a 'selected_skills' array containing skill names. Select only relevant skills. If none are relevant, return an empty array."), + new("user", request) + }; + + // JSON Schema for structured output. + string schema = """{"type":"object","properties":{"selected_skills":{"type":"array","items":{"type":"string"}}},"required":["selected_skills"]}"""; + using JsonDocument formatDoc = JsonDocument.Parse(schema); + + OllamaResult result; + try + { + result = await ollama.ChatStructuredAsync(selectionMessages, 0.0, formatDoc.RootElement.Clone(), cancellationToken).ConfigureAwait(false); + } + catch + { + // If structured output fails (e.g., model doesn't support it), fall back to no skills. + return []; + } + + return ParseSelectedSkills(result.Content); + } + + private IReadOnlyList<AgentSkill> ParseSelectedSkills(string content) + { + try + { + using JsonDocument doc = JsonDocument.Parse(content); + if (!doc.RootElement.TryGetProperty("selected_skills", out JsonElement skillsElement)) return []; + var selectedNames = new HashSet<string>(StringComparer.Ordinal); + foreach (JsonElement item in skillsElement.EnumerateArray()) + { + string? name = item.GetString(); + if (!string.IsNullOrWhiteSpace(name)) selectedNames.Add(name); + } + // Validate: only known skills, no duplicates, max 10. + var valid = new List<AgentSkill>(); + foreach (AgentSkill skill in settings.Skills) + { + if (selectedNames.Contains(skill.Name)) + { + valid.Add(skill); + if (valid.Count >= 10) break; + } + } + return valid; + } + catch + { + return []; + } + } + private async Task<string?> EndTaskSafelyAsync(Guid taskId, bool success, CancellationToken cancellationToken) { try |
