Tools.fromObject
Java · package io.github.muthuishere:toolnexus · SPEC §6 · Tools.java
public static List<Tool> fromObject(Object target)Scans every public method on target’s class for the @ToolMethod
annotation and turns each match into a Tool (source "native") — the object itself supplies
the method bodies, fromObject supplies the discovery + schema inference + Tool wrapping.
When to use it
Section titled “When to use it”When you have a plain object — a service, a small “tools” class — with one or more
@ToolMethod-annotated methods, and want every one of them as Tools in a single call, rather
than wrapping each with NativeTool.of by hand.
Why this and not the alternative
Section titled “Why this and not the alternative”Toolkit.Options.annotatedObjects calls fromObject on every object you add there internally —
use fromObject directly when you want the List<Tool> itself (to register selectively, filter,
or hand to an adapter) rather than a fully assembled Toolkit.
Examples
Section titled “Examples”1. Sweeping a class with two annotated methods
Section titled “1. Sweeping a class with two annotated methods”import io.github.muthuishere.toolnexus.*;import io.github.muthuishere.toolnexus.annotations.Param;import io.github.muthuishere.toolnexus.annotations.ToolMethod;import java.util.List;import java.util.Map;
public class Example { static final class MathTools { @ToolMethod(name = "add", description = "Add two numbers") public String add(@Param(name = "a") double a, @Param(name = "b") double b) { return String.valueOf(a + b); }
@ToolMethod(name = "negate", description = "Negate a number") public String negate(@Param(name = "a") double a) { return String.valueOf(-a); }
// Not annotated — never collected. public String helper() { return "not a tool"; } }
public static void main(String[] args) { List<Tool> tools = Tools.fromObject(new MathTools()); if (tools.size() != 2) throw new AssertionError("expected 2, got " + tools.size());
List<String> names = tools.stream().map(Tool::name).sorted().toList(); if (!names.equals(List.of("add", "negate"))) throw new AssertionError(names);
System.out.println("ok: " + names); }}2. Instance state — the object is captured, not just its class
Section titled “2. Instance state — the object is captured, not just its class”fromObject binds to a specific instance: methods run against that instance’s fields, so two
objects of the same class produce independent tools.
import io.github.muthuishere.toolnexus.*;import io.github.muthuishere.toolnexus.annotations.Param;import io.github.muthuishere.toolnexus.annotations.ToolMethod;import java.util.List;import java.util.Map;
public class Example { static final class Counter { private int count;
@ToolMethod(name = "increment", description = "Increment and return the counter") public String increment(@Param(name = "by", required = false) Integer by) { count += (by == null ? 1 : by); return String.valueOf(count); } }
public static void main(String[] args) { Counter a = new Counter(); Counter b = new Counter();
Tool toolA = Tools.fromObject(a).get(0); Tool toolB = Tools.fromObject(b).get(0);
toolA.execute(Map.of(), new ToolContext()); toolA.execute(Map.of(), new ToolContext()); ToolResult resA = toolA.execute(Map.of("by", 5), new ToolContext()); ToolResult resB = toolB.execute(Map.of(), new ToolContext());
if (!resA.output().equals("7")) throw new AssertionError(resA.output()); // 1 + 1 + 5 if (!resB.output().equals("1")) throw new AssertionError(resB.output()); // independent state
System.out.println("ok: a=" + resA.output() + " b=" + resB.output()); }}3. Registering collected tools alongside every other source
Section titled “3. Registering collected tools alongside every other source”The result is an ordinary List<Tool> — mix it into a Toolkit via register, exactly like a
tool from any other source.
import io.github.muthuishere.toolnexus.*;import io.github.muthuishere.toolnexus.annotations.Param;import io.github.muthuishere.toolnexus.annotations.ToolMethod;import java.util.List;import java.util.Map;
public class Example { static final class TimeTools { @ToolMethod(name = "epoch_zero", description = "Return the fixed epoch, for a deterministic doc example") public String epochZero() { return "1970-01-01T00:00:00Z"; } }
public static void main(String[] args) { try (Toolkit tk = Toolkit.create(new Toolkit.Options())) { for (Tool t : Tools.fromObject(new TimeTools())) { tk.register(t); }
Tool found = tk.get("epoch_zero"); if (found == null) throw new AssertionError("not registered"); if (!found.source().equals("native")) throw new AssertionError(found.source());
ToolResult res = found.execute(Map.of(), new ToolContext()); if (!res.output().equals("1970-01-01T00:00:00Z")) throw new AssertionError(res.output());
System.out.println("ok: " + found.name() + " -> " + res.output()); } }}Fields
Section titled “Fields”| Member | Type | What it is |
|---|---|---|
fromObject(target) |
List<Tool> |
One Tool per @ToolMethod-annotated public method on target’s class, in declaration order. |
| Unannotated methods | — | Silently skipped — never collected, never an error. |
| Binding | — | Each returned Tool.execute invokes the method against the same target instance passed in. |
See also
Section titled “See also”@ToolMethod annotation— what marks a method as collectible, and how its schema is inferred.NativeTool.of— build a singleToolby hand when you don’t want annotation-driven inference.Toolkit.create—annotatedObjects(...)callsfromObjectfor you.