diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-08-11 22:40:46 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-08-11 22:40:46 +0900 |
| commit | 37386b89b6272f4cef34ab3bf131f11ed3afa13f (patch) | |
| tree | b6e8a1827eb248c4a44bbe9e9a36dbe9e97c27e3 /src/Agent.Core | |
| parent | 8f8e82e00e5d0e9347feef10111faac788ad7d88 (diff) | |
Constrain structured planning responses
Diffstat (limited to 'src/Agent.Core')
| -rw-r--r-- | src/Agent.Core/Runtime/PlannerService.cs | 16 |
1 files changed, 13 insertions, 3 deletions
diff --git a/src/Agent.Core/Runtime/PlannerService.cs b/src/Agent.Core/Runtime/PlannerService.cs index b5ac80e..2a006f7 100644 --- a/src/Agent.Core/Runtime/PlannerService.cs +++ b/src/Agent.Core/Runtime/PlannerService.cs @@ -229,25 +229,34 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se Action<string, IReadOnlyList<OllamaMessage>, OllamaResult?, Exception?>? observeAttempt, Func<PlanResult, TaskGraph> buildGraph, CancellationToken cancellationToken) { Exception? firstFailure = null; + IReadOnlyList<OllamaMessage> attemptMessages = messages; for (int attempt = 0; attempt < 2; attempt++) { OllamaResult? result = null; try { bool? thinkOverride = attempt == 0 ? null : false; - result = await _ollama.ChatStructuredAsync(messages, temperature, PlanSchema.Element, thinkOverride, cancellationToken).ConfigureAwait(false); + result = await _ollama.ChatStructuredAsync(attemptMessages, temperature, PlanSchema.Element, thinkOverride, cancellationToken).ConfigureAwait(false); PlanResult? plan = ParsePlan(result.Content); if (plan is null || plan.Tasks.Count == 0) throw new PlannerValidationException("Ollama returned an empty or invalid structured plan."); TaskGraph graph = buildGraph(plan); - observeAttempt?.Invoke(phase, messages, result, null); + observeAttempt?.Invoke(phase, attemptMessages, result, null); return graph; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException or AccessViolationException)) { - observeAttempt?.Invoke(phase, messages, result, ex); + observeAttempt?.Invoke(phase, attemptMessages, result, ex); firstFailure ??= ex; + if (attempt == 0) + { + attemptMessages = messages.Concat([ + new OllamaMessage("system", + "The previous response was not a valid TaskGraph JSON object. Do not call or execute any Tool while planning. " + + "Return only one JSON object that matches the supplied schema, beginning with {\"tasks\":[ and containing no Markdown or commentary.") + ]).ToArray(); + } } } throw new PlannerValidationException($"Structured {phase} failed after think=false retry: {firstFailure?.Message}"); @@ -294,6 +303,7 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se private static string BuildPlanningInstruction() => """ You are a Task Planner. Decompose the user request into a TaskGraph of 1-32 tasks. + Planning is read-only: do not call or execute Tools. Return only one JSON object matching the supplied schema, with no Markdown or commentary. Each task must have: - description: a clear, self-contained sub-task description - dependencies: array of task indices (0-based) that must complete before this task |
