Skip to content

Annotation / reflection tools

Clojure (JVM) + cljgo · package net.clojars.muthuishere/toolnexus · SPEC §6 · clojure/src/toolnexus/native.cljc

Derive the schema from the function signature or annotation instead of writing it by hand.

toolnexus.native/native-tool is the whole native-tool surface here, and it always takes an explicit :input-schema. Write the JSON Schema as a Clojure map next to the function:

(require '[toolnexus.native :as native])
(def get-weather
(native/native-tool
{:name "get_weather"
:description "Current weather for a city"
:input-schema {:type "object"
:properties {:city {:type "string" :description "City name"}
:units {:type "string" :enum ["c" "f"] :default "c"}}
:required ["city"]}
:run (fn [args] (str "sunny in " (:city args)))}))
((:execute get-weather) {:city "Chennai"}) ;=> {:output "sunny in Chennai" :isError false}

A Clojure fn carries no argument names and no types at runtime on either host, so there is nothing for reflection to read even if the mechanism existed — a derived schema would be a guess about a dynamically typed function, and the model would be the one to discover the guess was wrong. Writing it out costs a few lines and buys a schema that is exactly what the endpoint accepts.

The schema is a value, so factor it the way you factor any other value when you have more than a handful of tools:

(require '[toolnexus.native :as native])
(defn- string-arg [desc] {:type "string" :description desc})
(defn- object-schema [props required]
{:type "object" :properties props :required required})
(def lookup-order
(native/native-tool
{:name "lookup_order"
:description "Fetch one order by id"
:input-schema (object-schema {:order-id (string-arg "The order id")} ["order-id"])
:run (fn [args] (str "order " (:order-id args)))}))

The positional arity is the same builder with the map flattened, which reads well when the schema is already a named value: (native/native-tool "lookup_order" "Fetch one order by id" schema f).