diff options
Diffstat (limited to 'src/Agent.Core/Runtime/AgentRuntime.cs')
| -rw-r--r-- | src/Agent.Core/Runtime/AgentRuntime.cs | 61 |
1 files changed, 39 insertions, 22 deletions
diff --git a/src/Agent.Core/Runtime/AgentRuntime.cs b/src/Agent.Core/Runtime/AgentRuntime.cs index 4aaf646..31a4414 100644 --- a/src/Agent.Core/Runtime/AgentRuntime.cs +++ b/src/Agent.Core/Runtime/AgentRuntime.cs @@ -426,34 +426,46 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag 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", task.Request) + new("system", $"You are a skill classifier, not a task executor. Do not solve the request, inspect files, write code, or call Tools. Available skills:\n{catalog}\n\nReturn only one JSON object with a 'selected_skills' array containing exact skill names. Select only relevant skills. If none are relevant, return an empty array."), + new("user", $"Classify skills for this request only:\n{task.Request}") }; string schema = """{"type":"object","properties":{"selected_skills":{"type":"array","items":{"type":"string"}}},"required":["selected_skills"]}"""; using JsonDocument formatDoc = JsonDocument.Parse(schema); - OllamaResult result; - AgentLlmCallContext? callContext = null; - try + IReadOnlyList<OllamaMessage> attemptMessages = selectionMessages; + for (int attempt = 0; attempt < 2; attempt++) { if (counters.Iterations >= settings.MaxIterations) return []; int iteration = counters.Iterations++; - callContext = new AgentLlmCallContext(Guid.NewGuid(), task.SessionId, task.TaskId, null, + var callContext = new AgentLlmCallContext(Guid.NewGuid(), task.SessionId, task.TaskId, null, "skill_selection", iteration, counters.Replans, settings.Model); - _observer.LlmRequest(callContext, selectionMessages); - result = await ollama.ChatStructuredAsync(selectionMessages, task.Temperature, formatDoc.RootElement.Clone(), cancellationToken).ConfigureAwait(false); - _observer.LlmResponse(callContext, result); - registry.AddUsage(task.TaskId, result.InputTokens, result.OutputTokens); - registry.UpdateDiagnostics(task.TaskId, counters.Iterations, counters.Replans); - } - catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException or AccessViolationException)) - { - if (callContext is not null) _observer.LlmFailure(callContext, "skill_selection_failed", ex.Message); - return []; + _observer.LlmRequest(callContext, attemptMessages); + OllamaResult? result = null; + try + { + bool? thinkOverride = attempt == 0 ? null : false; + result = await ollama.ChatStructuredAsync(attemptMessages, task.Temperature, formatDoc.RootElement.Clone(), thinkOverride, cancellationToken).ConfigureAwait(false); + _observer.LlmResponse(callContext, result); + registry.AddUsage(task.TaskId, result.InputTokens, result.OutputTokens); + registry.UpdateDiagnostics(task.TaskId, counters.Iterations, counters.Replans); + if (TryParseSelectedSkills(result.Content, out IReadOnlyList<AgentSkill> selected)) return selected; + throw new PlannerValidationException("Ollama returned an invalid skill selection object."); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } + catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException or AccessViolationException)) + { + _observer.LlmFailure(callContext, "skill_selection_failed", ex.Message); + if (attempt == 0) + { + attemptMessages = selectionMessages.Concat([ + new OllamaMessage("system", "The previous response was invalid. Return only {\"selected_skills\":[\"name\"]} or {\"selected_skills\":[]}, with no Markdown, code, Tool call, or commentary.") + ]).ToArray(); + } + } } - return ParseSelectedSkills(result.Content); + return []; } private void RecordLlmAttempt(TaskSnapshot task, ExecutionCounters counters, string phase, @@ -525,15 +537,19 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag }); } - private IReadOnlyList<AgentSkill> ParseSelectedSkills(string content) + private bool TryParseSelectedSkills(string content, out IReadOnlyList<AgentSkill> selected) { + selected = []; try { - using JsonDocument doc = JsonDocument.Parse(content); - if (!doc.RootElement.TryGetProperty("selected_skills", out JsonElement skillsElement)) return []; + using JsonDocument doc = JsonDocument.Parse(StructuredJson.Unwrap(content)); + if (doc.RootElement.ValueKind != JsonValueKind.Object || + !doc.RootElement.TryGetProperty("selected_skills", out JsonElement skillsElement) || + skillsElement.ValueKind != JsonValueKind.Array) return false; var selectedNames = new HashSet<string>(StringComparer.Ordinal); foreach (JsonElement item in skillsElement.EnumerateArray()) { + if (item.ValueKind != JsonValueKind.String) return false; string? name = item.GetString(); if (!string.IsNullOrWhiteSpace(name)) selectedNames.Add(name); } @@ -546,11 +562,12 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag if (valid.Count >= 10) break; } } - return valid; + selected = valid; + return true; } catch { - return []; + return false; } } |
