Skip to content

Tools.FromObject

C# · package Toolnexus · SPEC §6 · Tools.cs

public static List<ITool> FromObject(object target)

Order follows GetMethods(BindingFlags.Public | BindingFlags.Instance) — reflection’s own method enumeration order, not source order. Methods without [ToolMethod] are skipped entirely, including every inherited object method (ToString, Equals, GetHashCode, GetType).

You have a class — a service, a repository, a facade over some subsystem — with several methods already marked [ToolMethod], and you want the whole set as one call rather than building each tool by hand. It’s also how you’d expose the same instance’s tools to more than one toolkit or client without re-annotating anything.

1. Sweep a class with several annotated methods

Section titled “1. Sweep a class with several annotated methods”
using Toolnexus;
var svc = new FileOpsService();
var tools = Tools.FromObject(svc);
var names = tools.Select(t => t.Name).OrderBy(n => n, StringComparer.Ordinal).ToList();
if (!names.SequenceEqual(new[] { "list_files", "read_file" })) throw new Exception(string.Join(",", names));
if (tools.Any(t => t.Source != "native")) throw new Exception("expected every tool to be native");
Console.WriteLine($"ok: {string.Join(", ", names)}");
class FileOpsService
{
[ToolMethod(Name = "read_file", Description = "Read a file's contents")]
public string ReadFile(string path) => $"contents of {path}";
[ToolMethod(Name = "list_files", Description = "List files under a directory")]
public string ListFiles(string dir) => $"files under {dir}";
// No [ToolMethod] — never becomes a tool, no matter how public.
public string Helper() => "not a tool";
}

2. Unannotated methods, including inherited ones, are skipped

Section titled “2. Unannotated methods, including inherited ones, are skipped”
using Toolnexus;
var empty = new PlainService();
var tools = Tools.FromObject(empty);
// PlainService has public methods (ToString, Equals, DoWork) but none carry [ToolMethod].
if (tools.Count != 0) throw new Exception($"expected 0 tools, got {tools.Count}: {string.Join(",", tools.Select(t => t.Name))}");
Console.WriteLine("ok: an unannotated class collects 0 tools");
class PlainService
{
public string DoWork() => "done"; // not marked — not a tool
}

3. The collected tools drop straight into a toolkit

Section titled “3. The collected tools drop straight into a toolkit”
using Toolnexus;
var svc = new MathService();
var collected = Tools.FromObject(svc);
// Same path NativeTool tools take: ExtraTools on Toolkit.Options. The toolkit also carries the
// built-in tools by default, so don't assert on a total count.
await using var tk = await Toolkit.CreateAsync(new Toolkit.Options { ExtraTools = collected });
if (tk.Get("add") == null) throw new Exception($"toolkit missing 'add': {string.Join(",", tk.Tools().Select(t => t.Name))}");
var res = await tk.Get("add")!.ExecuteAsync(new Dictionary<string, object?> { ["a"] = 2, ["b"] = 3 });
if (res.Output != "5") throw new Exception(res.Output);
Console.WriteLine($"ok: add(2,3) -> {res.Output}");
class MathService
{
[ToolMethod(Name = "add", Description = "Add two integers")]
public string Add(int a, int b) => (a + b).ToString();
}
Parameter Type What it is
target object Any instance. Its public instance methods carrying [ToolMethod] become the returned tools.
  • NativeTool.Of — Wrap a plain function with a name, description and schema — the shortest path from code you have to a tool the LLM can call.
  • ToolMethodAttribute — Derive the schema from the function signature or annotation instead of writing it by hand.