skip to content

When and how would you build tools programmatically (FunctionToolCallback / ToolCallbacks.from) and pass out-of-band data with ToolContext?

level: middleimportance: should knowfreq 35%

answer

  1. ToolCallback = the real abstraction
  2. FunctionToolCallback.builder(name, fn).inputType(...)
  3. ToolCallbacks.from(bean)
  4. ToolContext = data model must NOT see
  5. .toolContext(Map.of(...)) per request

basics

~20 s

Besides @Tool methods, you can build a ToolCallback programmatically — e.g. FunctionToolCallback wraps a Function/BiFunction with a name, description and input type. ToolCallbacks.from(obj) turns @Tool methods into callbacks. ToolContext lets you pass data (like a user ID) to the tool without exposing it to the model.

solid answer

~50 s

Spring AI's tool abstraction is the ToolCallback interface; @Tool is just one way to produce them. For functional or bean-based tools use FunctionToolCallback.builder(name, function) with a description and inputType — ideal when the logic is a Function<I,O> or BiFunction. ToolCallbacks.from(myBean) reflects a bean's @Tool methods into an array of MethodToolCallbacks you can register with .toolCallbacks(...). ToolContext solves a key problem: some data (tenant/user id, security principal, DB handle) must reach the tool but must NOT be sent to or chosen by the model. You add a final ToolContext parameter to the method (or BiFunction), and supply values per request via .toolContext(Map.of("userId", id)). The model sees only the model-facing params in the schema; the ToolContext arg is injected by Spring and stays server-side. This keeps sensitive/contextual data out of the prompt while still available to the tool.

code

java · 17 lines
java
class OrderTools {
    private final OrderService orderService;
    OrderTools(OrderService orderService) { this.orderService = orderService; }

    // userId comes from ToolContext, NOT from the model
    @Tool(description = "List the current user's open orders")
    List<Order> myOpenOrders(ToolContext ctx) {
        String userId = (String) ctx.getContext().get("userId");
        return orderService.openOrders(userId);
    }
}

String reply = chatClient.prompt("Show my open orders")
        .tools(new OrderTools(orderService))
        .toolContext(Map.of("userId", currentUser.id()))
        .call()
        .content();

go deeper

for a junior

Aware that tools can also be plain functions and that some data is passed separately.

for a middle

Can build a FunctionToolCallback with inputType and use ToolContext for out-of-band data.

for a senior

Chooses the right registration path and enforces that sensitive/contextual data flows via ToolContext, not schema params.

for a principal

Treats ToolContext as a security boundary; designs tool APIs so the model never supplies identity/authorization inputs.

## The ToolCallback abstraction Under the hood every tool is a `org.springframework.ai.tool.ToolCallback`: it carries a **definition** (name, description, input JSON schema) and a `call(...)` method that executes it. The `@Tool` annotation is a convenience that produces `MethodToolCallback`s. Two other production paths: ### 1. ToolCallbacks.from(...) — from annotated beans ```java ToolCallback[] callbacks = ToolCallbacks.from(myAnnotatedBean); chatClient.prompt("...").toolCallbacks(callbacks).call().content(); ``` This reflects the bean's `@Tool` methods into callbacks. Handy when tools live on Spring beans and you want to register them explicitly (e.g. building options manually). ### 2. FunctionToolCallback — functional style When your tool is naturally a `Function<Request, Response>` (or `BiFunction`), wrap it: ```java ToolCallback weather = FunctionToolCallback .builder("currentWeather", new WeatherFunction()) .description("Get the weather for a location") .inputType(WeatherRequest.class) .build(); ``` `inputType` is required so Spring can generate the input schema and deserialize the model's JSON arguments into your request object. This style pairs well with registering `Function` beans and is the modern replacement for the older Spring AI *function callback* API (the 0.8.x `FunctionCallback`/`.functions("beanName")` was renamed to the tool API in 1.0). ## ToolContext — passing data the model must not see A frequent need: the tool requires context (authenticated user id, tenant, a repository, a correlation id) that: - the model should **not** choose (security/correctness), and - should **not** appear in the prompt (privacy, token cost). `org.springframework.ai.chat.model.ToolContext` carries an arbitrary `Map<String,Object>` that Spring injects into the tool but never advertises in the schema. Method-based: ```java @Tool(description = "List the current user's open orders") List<Order> myOrders(ToolContext ctx) { String userId = (String) ctx.getContext().get("userId"); return orderService.openOrders(userId); } ``` Functional style uses a `BiFunction<Request, ToolContext, Response>`. Supply values per request: ```java chatClient.prompt("What are my open orders?") .tools(new OrderTools()) .toolContext(Map.of("userId", currentUser.id())) .call().content(); ``` The `ToolContext` parameter is **omitted from the JSON schema**, so the model can't set `userId` — it's injected server-side. This is the idiomatic way to bind a tool to the authenticated caller rather than trusting the model. ## When to use which - **@Tool methods** — default, most readable, for domain services. - **ToolCallbacks.from(bean)** — when you need the callback array explicitly (custom options, dynamic assembly). - **FunctionToolCallback** — when logic is already a `Function`/lambda or comes from a functional bean registry. - **ToolContext** — whenever a tool needs per-request or sensitive data that must stay out of the model. ## Gotchas - Forgetting `inputType` on `FunctionToolCallback` breaks schema generation/deserialization. - Putting sensitive ids as normal `@ToolParam` args exposes them to the model and lets it fabricate them — use `ToolContext` instead. - `ToolContext` values are per-request; they aren't persisted across calls automatically.

  • Why pass a user id via ToolContext instead of as a @ToolParam?
    A @ToolParam appears in the schema, so the model both sees it and chooses its value — it could fabricate or leak another user's id, and it costs tokens. ToolContext injects the value server-side, invisible to the model, so the tool is bound to the authenticated caller securely.
  • What must you set when building a FunctionToolCallback, and why?
    You must set inputType (the request class). Spring uses it to generate the input JSON schema shown to the model and to deserialize the model's JSON arguments into a typed object before calling your Function.

saying these in an interview costs you the question

  • Passing secrets/user ids as normal tool parameters the model chooses
  • Thinking @Tool is the only way to create a tool (ToolCallback is the abstraction)
  • Omitting inputType on FunctionToolCallback

context