Ollama/ToolCalling/ToolRegistry.cscsharp

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.

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;    }}