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.
- Provide the tool to the model using the
toolsproperty. - When the model receives a user message, it will analyze the message and determine if it needs to call any of the provided tools.
- If the model decides to call a function, it will invoke the function with the required arguments.
- The function is executed within Angular's injection context
- Return the result that is sent back to the LLM.
The createTool() Function
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();
},
});
- Use the createTool function to define a function that the LLM can call.
- The
nameproperty is the name of the function that the LLM will call. - The
descriptionproperty is a description of what the function does. This is used by the LLM to determine if it should call the function. - The
handlerproperty 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 anAbortSignaland is expected to return aPromise.
CreateToolOptions
| Option | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The name of the function that the LLM will call |
description | string | Yes | Description of what the function does |
schema | s.SchemaInput | No | Schema defining the function arguments |
handler | Function | Yes | The function to execute when called |
Schema Inputs
The schema option accepts:
- Skillet schemas (
s.string,s.object, etc.) - Standard JSON Schema objects (the
~standardspec, 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: (
input: s.InferSchemaInput<Schema>,
abortSignal: AbortSignal
) => Promise<Result>;
Without input Arguments:
handler: (abortSignal: AbortSignal) => Promise<Result>;
Providing the Tools
Provide the tools when using Hashbrown's resource-based APIs.
@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.
- First, we define the
toolsarray in thechatResourceconfiguration. - We use the createTool function to define each tool.
- The
getUser()function returns the authenticated user information to the model. - The
getLights()function returns application state to the model. - 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.
| Status | Meaning |
|---|---|
inProgress | Argument deltas are still arriving; args may be partial |
executing | The arguments are complete; the server is running the tool |
complete | The 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.
@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.