using System.Text.Json;using Agent.Core.Ollama;namespace Agent.Core.ToolCalling;/// <summary>Registry of tools exposed to Ollama tool-calling (Version 3+).</summary>public sealed class ToolRegistry{ /// <summary>All user/system registered tools. Never sent to Ollama directly.</summary> private readonly Dictionary<string, AgentTool> _catalog = new(StringComparer.OrdinalIgnoreCase); /// <summary>Only the two meta-tools exposed to Ollama to conserve context window.</summary> private readonly Dictionary<string, AgentTool> _tools = new(StringComparer.OrdinalIgnoreCase); public event Action<string>? OnNameChanged; public event Action<string>? OnVoiceChanged; public Memory.MemoryManager? Memory { get; set; } public void InvokeNameChanged(string newName) => OnNameChanged?.Invoke(newName); public void InvokeVoiceChanged(string newVoice) => OnVoiceChanged?.Invoke(newVoice); public ToolRegistry() { RegisterMetaTools(); } private void RegisterMetaTools() { _tools["search_tools"] = new AgentTool( "search_tools", "Search for available tools by keyword. Returns a JSON list of matching tool names and descriptions. Call with an empty query to list all tools.", SearchTools, new { type = "object", properties = new { query = new { type = "string", description = "Keyword to search for in tool names and descriptions. Leave empty to list all tools." } } }); _tools["execute_tool"] = new AgentTool( "execute_tool", "Execute a specific tool by name with the provided arguments.", ExecuteTool, new { type = "object", required = new[] { "tool_name" }, properties = new { tool_name = new { type = "string", description = "The exact name of the tool to execute (from search_tools)." }, tool_arguments = new { type = "object", description = "Arguments to pass to the tool as a JSON object." } } }); } private string SearchTools(string argsJson) { string query = string.Empty; try { using var doc = JsonDocument.Parse(argsJson); if (doc.RootElement.TryGetProperty("query", out var q)) query = q.GetString() ?? string.Empty; } catch { } var matches = _catalog.Values .Where(t => string.IsNullOrWhiteSpace(query) || t.Name.Contains(query, StringComparison.OrdinalIgnoreCase) || t.Description.Contains(query, StringComparison.OrdinalIgnoreCase)) .Select(t => new { name = t.Name, description = t.Description }) .ToArray(); return JsonSerializer.Serialize(matches); } private string ExecuteTool(string argsJson) { string toolName = string.Empty; string toolArgs = "{}"; try { using var doc = JsonDocument.Parse(argsJson); if (doc.RootElement.TryGetProperty("tool_name", out var nameProp)) toolName = nameProp.GetString() ?? string.Empty; if (doc.RootElement.TryGetProperty("tool_arguments", out var argsProp) && argsProp.ValueKind != JsonValueKind.Null && argsProp.ValueKind != JsonValueKind.Undefined) toolArgs = argsProp.GetRawText(); } catch { } if (string.IsNullOrWhiteSpace(toolName)) return "Error: tool_name is required."; if (!_catalog.TryGetValue(toolName, out AgentTool? tool)) return $"Error: Unknown tool '{toolName}'. Use search_tools to find available tools."; try { return tool.Invoke(toolArgs); } catch (Exception ex) { return $"Tool '{toolName}' failed: {ex.Message}"; } } public void Register(AgentTool tool) { ArgumentNullException.ThrowIfNull(tool); _catalog[tool.Name] = tool; } public bool TryInvoke(string name, string? argumentsJson, out string result) { // Check meta-tools first (search_tools, execute_tool) if (_tools.TryGetValue(name, out AgentTool? metaTool)) { try { result = metaTool.Invoke(argumentsJson ?? "{}"); return true; } catch (Exception ex) { result = $"Tool '{name}' failed: {ex.Message}"; return false; } } // Fall back to catalog for direct invocation (e.g., unit tests) if (!_catalog.TryGetValue(name, out AgentTool? catalogTool)) { result = $"Unknown tool: {name}"; return false; } try { result = catalogTool.Invoke(argumentsJson ?? "{}"); return true; } catch (Exception ex) { result = $"Tool '{name}' failed: {ex.Message}"; return false; } } internal IReadOnlyList<OllamaToolDefinitionDef> ToOllamaDefinitions() { return _tools.Values .Select(t => new OllamaToolDefinitionDef( "function", new OllamaToolFunctionDef(t.Name, t.Description, t.ParametersSchema))) .ToList(); } public static ToolRegistry CreateDefault() { var registry = new ToolRegistry(); BuiltInTools.Register(registry); return registry; }}
Documentation
Tool calling (Version 3)
Agent-Core exposes a small tool registry for Ollama function calling. Lilith registers these tools on the chat client at boot.
| Tool | Description | |------|-------------| | get_time | Current local time | | get_date | Today's local date |
Register more tools with ToolRegistry.Register before chat starts.