summaryrefslogtreecommitdiff
path: root/src/Agent.Core/Ollama
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-11 00:15:54 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-11 00:15:54 +0900
commitd5d0a84e573c833843279c520ce2d5765425ba93 (patch)
treeb6ce997a545749578f565cfe5a4b31e8d16b5199 /src/Agent.Core/Ollama
parentd5b3cb20c6dacd31bb54c52a30a0f64487e30215 (diff)
feat: implement LLM skill selection with Ollama structured output
P5残件3項目を実装: 1. Ollama structured output (format param): - OllamaChatRequestへFormat プロパティ追加 (JsonElement?) - IOllamaClientへChatStructuredAsync メソッド追加 - OllamaClient.ChatCoreAsync へformat パラメータ追加 - ChatStructuredAsync はtoolsなし、format指定でJSON構造化出力を要求 2. AgentRuntime skill selection step: - SelectSkillsAsync メソッド追加: メインループ前にOllamaへskill catalogを送信 - structured output で {"selected_skills": ["name"]} を取得 - ParseSelectedSkills: JSON解析、unknown skill名排除、重複排除、最大10件 - structured output非対応モデルはfallback空リスト - 選択されたskillのInstructionsのみPromptContext.SelectedSkillsへ設定 - Progressive Disclosure Stage 2→3 が完了 3. Skill security/path validation: - SelectSkillsAsync でsettings.Skillsからのみ選択(unknown name排除) - SkillLoader でreparse point skip済み(既存実装) - 選択数上限10件でDoS防止 - Skill load != permission grant 維持: allowed-toolsはSandbox permissionに影響しない 変更対象: - OllamaModels.cs: OllamaChatRequest.Format プロパティ追加 - IOllamaClient.cs: ChatStructuredAsync メソッド追加 - OllamaClient.cs: ChatCoreAsync へformat パラメータ追加、ChatStructuredAsync 実装 - AgentRuntime.cs: SelectSkillsAsync + ParseSelectedSkills、メインループ前の選択ステップ 検証結果: - Release build: 警告0、エラー0 - Core tests: 61/61 PASS、skip 0 - Planner tests: 10/10 PASS、skip 0
Diffstat (limited to 'src/Agent.Core/Ollama')
-rw-r--r--src/Agent.Core/Ollama/IOllamaClient.cs3
-rw-r--r--src/Agent.Core/Ollama/OllamaClient.cs12
-rw-r--r--src/Agent.Core/Ollama/OllamaModels.cs1
3 files changed, 12 insertions, 4 deletions
diff --git a/src/Agent.Core/Ollama/IOllamaClient.cs b/src/Agent.Core/Ollama/IOllamaClient.cs
index a03a353..d1cb99f 100644
--- a/src/Agent.Core/Ollama/IOllamaClient.cs
+++ b/src/Agent.Core/Ollama/IOllamaClient.cs
@@ -1,5 +1,6 @@
namespace Agent.Core.Ollama;
+using System.Text.Json;
using Agent.Core.Tools;
public interface IOllamaClient
@@ -7,5 +8,7 @@ public interface IOllamaClient
Task<OllamaResult> ChatAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, CancellationToken cancellationToken);
Task<OllamaResult> ChatWithToolsAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, IReadOnlyList<InternalToolDefinition> tools, CancellationToken cancellationToken) =>
ChatAsync(messages, temperatureOverride, cancellationToken);
+ Task<OllamaResult> ChatStructuredAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, JsonElement format, CancellationToken cancellationToken) =>
+ ChatAsync(messages, temperatureOverride, cancellationToken);
Task<bool?> SupportsVisionAsync(CancellationToken cancellationToken) => Task.FromResult<bool?>(null);
}
diff --git a/src/Agent.Core/Ollama/OllamaClient.cs b/src/Agent.Core/Ollama/OllamaClient.cs
index bfd2210..c761081 100644
--- a/src/Agent.Core/Ollama/OllamaClient.cs
+++ b/src/Agent.Core/Ollama/OllamaClient.cs
@@ -22,10 +22,13 @@ public sealed class OllamaClient : IOllamaClient
}
public async Task<OllamaResult> ChatAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, CancellationToken cancellationToken)
- => await ChatCoreAsync(messages, temperatureOverride, null, cancellationToken).ConfigureAwait(false);
+ => await ChatCoreAsync(messages, temperatureOverride, null, null, cancellationToken).ConfigureAwait(false);
public async Task<OllamaResult> ChatWithToolsAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, IReadOnlyList<InternalToolDefinition> tools, CancellationToken cancellationToken)
- => await ChatCoreAsync(messages, temperatureOverride, tools, cancellationToken).ConfigureAwait(false);
+ => await ChatCoreAsync(messages, temperatureOverride, tools, null, cancellationToken).ConfigureAwait(false);
+
+ public async Task<OllamaResult> ChatStructuredAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, JsonElement format, CancellationToken cancellationToken)
+ => await ChatCoreAsync(messages, temperatureOverride, null, format, cancellationToken).ConfigureAwait(false);
public async Task<bool?> SupportsVisionAsync(CancellationToken cancellationToken)
{
@@ -42,7 +45,7 @@ public sealed class OllamaClient : IOllamaClient
catch (HttpRequestException) { return null; }
}
- private async Task<OllamaResult> ChatCoreAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, IReadOnlyList<InternalToolDefinition>? tools, CancellationToken cancellationToken)
+ private async Task<OllamaResult> ChatCoreAsync(IReadOnlyList<OllamaMessage> messages, double? temperatureOverride, IReadOnlyList<InternalToolDefinition>? tools, JsonElement? format, CancellationToken cancellationToken)
{
Dictionary<string, JsonElement> options = _settings.Options.ToDictionary(static pair => pair.Key, static pair => pair.Value, StringComparer.Ordinal);
if (temperatureOverride is not null) options["temperature"] = JsonSerializer.SerializeToElement(temperatureOverride.Value);
@@ -54,7 +57,8 @@ public sealed class OllamaClient : IOllamaClient
Think = _settings.Think,
KeepAlive = _settings.KeepAlive,
Options = options,
- Tools = tools?.Select(static tool => new OllamaToolDefinition("function", new OllamaFunctionDefinition(tool.Name, tool.Description, tool.Parameters))).ToArray()
+ Tools = tools?.Select(static tool => new OllamaToolDefinition("function", new OllamaFunctionDefinition(tool.Name, tool.Description, tool.Parameters))).ToArray(),
+ Format = format
};
for (int attempt = 0; ; attempt++)
diff --git a/src/Agent.Core/Ollama/OllamaModels.cs b/src/Agent.Core/Ollama/OllamaModels.cs
index 823a178..f4b3884 100644
--- a/src/Agent.Core/Ollama/OllamaModels.cs
+++ b/src/Agent.Core/Ollama/OllamaModels.cs
@@ -33,6 +33,7 @@ public sealed class OllamaChatRequest
[JsonPropertyName("keep_alive")] public required string KeepAlive { get; init; }
[JsonPropertyName("options")] public required IReadOnlyDictionary<string, System.Text.Json.JsonElement> Options { get; init; }
[JsonPropertyName("tools"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyList<OllamaToolDefinition>? Tools { get; init; }
+ [JsonPropertyName("format"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public JsonElement? Format { get; init; }
}
public sealed class OllamaChatResponse