hashbrown

Tool Calling

Give the model access to your application state and enable the model to take action.

Tool calling (or function calling) in Hashbrown provides an intuitive approach to describing the tools that the model has access to.

  • Execute a function in Angular's injection context.
  • Return data to the model from state or an Angular service.

Demo


How it Works

When you define a tool using Hashbrown's createTool function the model can choose to use the tool to follow instructions and respond to prompts.

  1. Provide the tool to the model using the tools property.
  2. When the model receives a user message, it will analyze the message and determine if it needs to call any of the provided tools.
  3. If the model decides to call a function, it will invoke the function with the required arguments.
  4. The function is executed within Angular's injection context
  5. Return the result that is sent back to the LLM.

The createTool() Function

createTool
import { createTool } from '@hashbrownai/angular';

createTool({
  name: 'getUser',
  description: 'Get information about the current user',
  handler: (): Promise<User> => {
    const authService = inject(AuthService);
    return authService.getUser();
  },
});
  1. Use the createTool function to define a function that the LLM can call.
  2. The name property is the name of the function that the LLM will call.
  3. The description property is a description of what the function does. This is used by the LLM to determine if it should call the function.
  4. The handler property is the function that will be called when the LLM invokes the function. This is where you can perform any logic you need, such as fetching data from a service or performing a task. The function is invoked with an AbortSignal and is expected to return a Promise.

CreateToolOptions

OptionTypeRequiredDescription
namestringYesThe name of the function that the LLM will call
descriptionstringYesDescription of what the function does
schemas.SchemaInputNoSchema defining the function arguments
handlerFunctionYesThe function to execute when called

Schema Inputs

The schema option accepts:

  • Skillet schemas (s.string, s.object, etc.)
  • Standard JSON Schema objects (the ~standard spec, e.g. Zod/ArkType)
  • Raw JSON Schema objects

Standard JSON Schema inputs are normalized to Skillet (draft-07) before streaming and validation. Unsupported keywords throw at runtime.


API Reference


Handler Signatures

With input Arguments:

handler
handler: (
  input: s.InferSchemaInput<Schema>,
  abortSignal: AbortSignal
) => Promise<Result>;

Without input Arguments:

handler
handler: (abortSignal: AbortSignal) => Promise<Result>;

Providing the Tools

Provide the tools when using Hashbrown's resource-based APIs.

tools
@Component({
  selector: 'app-chat',
  providers: [LightsStore],
  template: ` <!-- omitted for brevity - full code in stackblitz example --> `,
})
export class ChatComponent {
  lightsStore = inject(LightsStore);

  chat = chatResource({
    // 1. Specify the tools collection using createTool() function
    tools: [
      //2. The getUser() function returns authenticated user information to model
      createTool({
        name: 'getUser',
        description: 'Get information about the current user',
        handler: () => {
          const authService = inject(AuthService);
          return authService.getUser();
        },
      }),
      // 3. The getLights() function returns application state to the model
      createTool({
        name: 'getLights',
        description: 'Get the current lights',
        handler: async () => this.lightsStore.entities(),
      }),
      // 4. The controlLight() function enables the model to mutate state
      createTool({
        name: 'controlLight',
        description: 'Control a light',
        schema: s.object('Control light input', {
          lightId: s.string('The id of the light'),
          brightness: s.number('The brightness of the light'),
        }),
        handler: async (input) =>
          this.lightsStore.updateLight(input.lightId, {
            brightness: input.brightness,
          }),
      }),
    ],
  });

  sendMessage(message: string) {
    this.chat.sendMessage({ role: 'user', content: message });
  }
}

Let's review the code above.

  1. First, we define the tools array in the chatResource configuration.
  2. We use the createTool function to define each tool.
  3. The getUser() function returns the authenticated user information to the model.
  4. The getLights() function returns application state to the model.
  5. The controlLight() function enables the model to mutate application state.

Server-Executed Tool Calls

Not every tool the model calls runs in the browser. An agent server can execute its own tools and stream only the call and its result to Hashbrown. Those calls appear on the assistant message as serverToolCalls, a display-only list that Hashbrown never executes. Each entry carries the tool's name, its toolCallId, a status, and args: the arguments resolved so far, growing as the server streams them, or null before any JSON has parsed.

StatusMeaning
inProgressArgument deltas are still arriving; args may be partial
executingThe arguments are complete; the server is running the tool
completeThe server reported the tool's result

Because args resolve progressively, a component can render from a server call before it finishes, the way it does for streaming text.

server tool calls
@Component({
  selector: 'app-answer',
  template: `
    @for (call of message().serverToolCalls ?? []; track call.toolCallId) {
      @if (call.name === 'render' && call.args) {
        <app-draft [args]="call.args" />
      }
    }
  `,
})
export class AnswerComponent {
  readonly message = input.required<UiAssistantMessage>();
}

Tools you register with createTool keep appearing in toolCalls exactly as before; only calls with no matching client tool land in serverToolCalls.


Next Steps

Tool CallingDemoHow it WorksThe createTool() FunctionCreateToolOptionsSchema InputsAPI ReferenceProviding the ToolsServer-Executed Tool CallsNext Steps